Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4053cea
Enable contracts for new holders and refactor related initialization …
AaronRobinsonMSFT Jul 29, 2026
2f14e6a
Implement ReleaseHolder release-path contract enforcement and add hos…
AaronRobinsonMSFT Jul 31, 2026
35e2b07
Scope contracts to a host and move EEContract into contract.h
AaronRobinsonMSFT Aug 4, 2026
8e53aeb
Add EE_THREAD_NOT_REQUIRED macro definition and clean up redundant code
AaronRobinsonMSFT Aug 4, 2026
ffaf77b
Reintroduce EE_THREAD_NOT_REQUIRED macro definition in contract.h
AaronRobinsonMSFT Aug 5, 2026
e5e723b
Annotate GC_NOTRIGGER on non-GC-triggering utilcode and md functions
AaronRobinsonMSFT Aug 6, 2026
5f6979f
Relax CCompRC::LoadString contract to MODE_ANY
AaronRobinsonMSFT Aug 6, 2026
9838b6c
Address contract violations
AaronRobinsonMSFT Aug 7, 2026
16ce2cf
Merge commit
AaronRobinsonMSFT Aug 7, 2026
64c07ac
Add GC_NOTRIGGER to various CONTRACTL blocks to prevent GC during spe…
AaronRobinsonMSFT Aug 7, 2026
4dc0b34
Add GC_NOTRIGGER to metadata pool rehash helpers; tidy exception thro…
AaronRobinsonMSFT Aug 10, 2026
619999c
Remove ReleaseHolder contract validation hooks and simplify contract …
AaronRobinsonMSFT Aug 10, 2026
0e73e6c
Add GC_NOTRIGGER to contract initialization methods to prevent GC dur…
AaronRobinsonMSFT Aug 10, 2026
70bd59f
Add GC_NOTRIGGER to metadata pool InitNew helpers
AaronRobinsonMSFT Aug 11, 2026
1373f84
Merge in main. Resolve conflict.
AaronRobinsonMSFT Aug 11, 2026
14a85ac
Implement ReleaseHolder release-path contract enforcement for hosted …
AaronRobinsonMSFT Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/debug/ee/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11762,7 +11762,7 @@ void Debugger::GetAndSendTransitionStubInfo(CORDB_ADDRESS_TYPE *stubAddress)
// If its not a stub, then maybe its an address in mscoree?
if (result == false)
{
result = (IsIPInModule(GetClrModuleBase(), (PCODE)stubAddress) == TRUE);
result = (g_pEEInterface->IsIPInModule(GetClrModuleBase(), (PCODE)stubAddress) == TRUE);
}

// This is a synchronous event (reply required)
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/gc/env/gcenv.sync.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ class CrstStatic
#endif

public:
bool InitNoThrow(CrstType eType, CrstFlags eFlags = CRST_DEFAULT)
void Init(CrstType eType, CrstFlags eFlags = CRST_DEFAULT)
{
return m_cs.Initialize();
(void)m_cs.Initialize();
}
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.

void Destroy()
Expand Down
7 changes: 1 addition & 6 deletions src/coreclr/gc/handletable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,7 @@ HHANDLETABLE HndCreateHandleTable(const uint32_t *pTypeFlags, uint32_t uTypeCoun
// We need to allow CRST_UNSAFE_SAMELEVEL, because
// during AD unload, we need to move some TableSegment from unloaded domain to default domain.
// We need to take both locks for the two HandleTable's to avoid racing with concurrent gc thread.
if (!pTable->Lock.InitNoThrow(CrstHandleTable, CrstFlags(CRST_REENTRANCY | CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_UNSAFE_SAMELEVEL)))
{
SegmentFree(pTable->pSegmentList);
delete [] (uint8_t*)pTable;
return NULL;
}
pTable->Lock.Init(CrstHandleTable, CrstFlags(CRST_REENTRANCY | CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD | CRST_UNSAFE_SAMELEVEL));

// remember how many types we are supporting
pTable->uTypeCount = uTypeCount;
Expand Down
11 changes: 4 additions & 7 deletions src/coreclr/inc/contract.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,18 +188,16 @@
#pragma warning(disable:4189) //local variable is initialized but not referenced
#endif


// We only enable contracts in _DEBUG builds
#if defined(_DEBUG) && !defined(DISABLE_CONTRACTS) && !defined(JIT_BUILD)
#if defined(_DEBUG) && !defined(DISABLE_CONTRACTS)
#define ENABLE_CONTRACTS_DATA
#endif

// Also, we won't enable contracts if this is a DAC build.
#if defined(ENABLE_CONTRACTS_DATA) && !defined(DACCESS_COMPILE) && !defined(CROSS_COMPILE)
// The DAC builds reference contract data but not implementation,
// so we disable contracts for those builds.
#if defined(ENABLE_CONTRACTS_DATA) && !defined(DACCESS_COMPILE)
#define ENABLE_CONTRACTS
#endif

// Finally, only define the implementation parts of contracts if this isn't a DAC build.
#if defined(_DEBUG_IMPL) && defined(ENABLE_CONTRACTS)
#define ENABLE_CONTRACTS_IMPL
#endif
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.
Expand Down Expand Up @@ -1739,5 +1737,4 @@ extern Volatile<LONG> g_DbgSuppressAllocationAsserts;
STATIC_CONTRACT_GC_TRIGGERS; \
STATIC_CONTRACT_MODE_PREEMPTIVE;


#endif // CONTRACT_H_
22 changes: 19 additions & 3 deletions src/coreclr/inc/holder.h
Original file line number Diff line number Diff line change
Expand Up @@ -991,16 +991,32 @@ class LifetimeHolder final
}
};

#ifdef ENABLE_CONTRACTS_IMPL
// Out-of-line hook that enforces the ReleaseHolder release-path contract
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.
// (NOTHROW / GC_TRIGGERS / MODE_PREEMPTIVE).
//
// Exactly one definition is linked into each binary, selected by which utilcode flavor
// that binary uses - the two are mutually exclusive, so there is never a collision or a
// gap:
// * the hosted EE (coreclr.dll / static host) provides the enforcing definition
// * hostless and tool builds provide a no-op stub
void ContractReleaseValidate();
#endif // ENABLE_CONTRACTS_IMPL

template <typename TYPE>
struct ReleaseHolderTraits final
{
using Type = TYPE*;
static constexpr Type Default() { return NULL; }
static void Free(Type value)
{
STATIC_CONTRACT_NOTHROW;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_PREEMPTIVE;
STATIC_CONTRACT_NOTHROW;
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_PREEMPTIVE;

#ifdef ENABLE_CONTRACTS_IMPL
ContractReleaseValidate();
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.
Outdated
#endif // ENABLE_CONTRACTS_IMPL

if (value != NULL)
value->Release();
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/inc/metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,7 @@ class HENUMInternalHolder
{
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;

_ASSERTE(!m_fAcquired);
Expand All @@ -1251,6 +1252,7 @@ class HENUMInternalHolder
{
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;

_ASSERTE(!m_fAcquired);
Expand All @@ -1267,6 +1269,7 @@ class HENUMInternalHolder
{
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;

_ASSERTE(!m_fAcquired);
Expand All @@ -1282,6 +1285,7 @@ class HENUMInternalHolder
{
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;

_ASSERTE(!m_fAcquired);
Expand Down
2 changes: 0 additions & 2 deletions src/coreclr/inc/utilcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -3453,8 +3453,6 @@ namespace util

INDEBUG(BOOL DbgIsExecutable(LPVOID lpMem, SIZE_T length);)

BOOL IsIPInModule(PTR_VOID pModuleBaseAddress, PCODE ip);

namespace UtilCode
{
// These are type-safe versions of Interlocked[Compare]Exchange
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/nativeaot/Runtime/Crst.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class CrstStatic
{
public:
void Init(CrstType eType, CrstFlags eFlags = CRST_DEFAULT);
bool InitNoThrow(CrstType eType, CrstFlags eFlags = CRST_DEFAULT) { Init(eType, eFlags); return true; }
void Destroy();
void Enter() { CrstStatic::Enter(this); }
void Leave() { CrstStatic::Leave(this); }
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/nativeaot/Runtime/GcStressControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class GcStressControl
volatile InitState is = (InitState) PalInterlockedCompareExchange((volatile int32_t*)(&s_initState), isIniting, isNotInited);
if (is == isNotInited)
{
s_lock.InitNoThrow(CrstGcStressControl);
s_lock.Init(CrstGcStressControl);

if (g_pRhConfig->GetGcStressSeed())
s_lGcStressRNGSeed = (uint32_t)g_pRhConfig->GetGcStressSeed();
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/nativeaot/Runtime/clrgc.enabled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ BOOL g_gcEventTracingInitialized = FALSE;

void InitializeGCEventLock()
{
g_eventStashLock.InitNoThrow(CrstGcEvent);
g_eventStashLock.Init(CrstGcEvent);
}

HRESULT InitializeStandaloneGC();
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ ep_rt_aot_spin_lock_alloc (ep_rt_spin_lock_handle_t *spin_lock)
// EventPipe library will intialize using thread, EventPipeBufferManager instances and will maintain these on the EventPipe library side

spin_lock->lock = new (nothrow) CrstStatic ();
spin_lock->lock->InitNoThrow (CrstType::CrstEventPipe);
spin_lock->lock->Init (CrstType::CrstEventPipe);
}

void
Expand Down Expand Up @@ -792,7 +792,7 @@ void ep_rt_aot_init (void)
extern CrstStatic _ep_rt_aot_config_lock;

_ep_rt_aot_config_lock_handle.lock = &_ep_rt_aot_config_lock;
_ep_rt_aot_config_lock_handle.lock->InitNoThrow (CrstType::CrstEventPipeConfig);
_ep_rt_aot_config_lock_handle.lock->Init (CrstType::CrstEventPipeConfig);
}

bool ep_rt_aot_lock_acquire (ep_rt_lock_handle_t *lock)
Expand Down
11 changes: 11 additions & 0 deletions src/coreclr/utilcode/hostimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,14 @@ void CreateCrashDumpIfEnabled(bool stackoverflow)
{
}
#endif

#if defined(ENABLE_CONTRACTS_IMPL) && defined(SELF_NO_HOST)
// Hostless counterpart to the other stubs in this file: with no EE there is nothing to
// enforce, so the ReleaseHolder release-path contract hook (declared in holder.h) is a
// no-op here.
// See definition in holder.h for more details.
void ContractReleaseValidate()
{
LIMITED_METHOD_CONTRACT;
}
#endif // ENABLE_CONTRACTS_IMPL && SELF_NO_HOST
93 changes: 0 additions & 93 deletions src/coreclr/utilcode/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2324,99 +2324,6 @@ void PutRiscV64AuipcCombo(UINT32 * pCode, INT64 offset, bool isStype)
_ASSERTE(GetRiscV64AuipcCombo(pCode, isStype) == offset);
}

//======================================================================
// This function returns true, if it can determine that the instruction pointer
// refers to a code address that belongs in the range of the given image.
BOOL IsIPInModule(PTR_VOID pModuleBaseAddress, PCODE ip)
{
STATIC_CONTRACT_LEAF;
SUPPORTS_DAC;

struct Param
{
PTR_VOID pModuleBaseAddress;
PCODE ip;
BOOL fRet;
} param;
param.pModuleBaseAddress = pModuleBaseAddress;
param.ip = ip;
param.fRet = FALSE;

// UNIXTODO: implement a proper version for PAL
#ifdef HOST_WINDOWS
PAL_TRY(Param *, pParam, &param)
{
PTR_BYTE pBase = dac_cast<PTR_BYTE>(pParam->pModuleBaseAddress);

PTR_IMAGE_DOS_HEADER pDOS = NULL;
PTR_IMAGE_NT_HEADERS pNT = NULL;
USHORT cbOptHdr;
PCODE baseAddr;

//
// First, must validate the format of the PE headers to make sure that
// the fields we're interested in using exist in the image.
//

// Validate the DOS header.
pDOS = PTR_IMAGE_DOS_HEADER(pBase);
if (pDOS->e_magic != VAL16(IMAGE_DOS_SIGNATURE) ||
pDOS->e_lfanew == 0)
{
goto lDone;
}

// Validate the NT header
pNT = PTR_IMAGE_NT_HEADERS(pBase + VAL32(pDOS->e_lfanew));

if (pNT->Signature != VAL32(IMAGE_NT_SIGNATURE))
{
goto lDone;
}

// Validate that the optional header is large enough to contain the fields
// we're interested, namely IMAGE_OPTIONAL_HEADER::SizeOfImage. The reason
// we don't just check that SizeOfOptionalHeader == IMAGE_SIZEOF_NT_OPTIONAL_HEADER
// is due to VSW443590, which states that the extensibility of this structure
// is such that it is possible to include only a portion of the optional header.
cbOptHdr = pNT->FileHeader.SizeOfOptionalHeader;

// Check that the magic field is contained by the optional header and set to the correct value.
if (cbOptHdr < (offsetof(IMAGE_OPTIONAL_HEADER, Magic) + sizeofmember(IMAGE_OPTIONAL_HEADER, Magic)) ||
pNT->OptionalHeader.Magic != VAL16(IMAGE_NT_OPTIONAL_HDR_MAGIC))
{
goto lDone;
}

// Check that the SizeOfImage is contained by the optional header.
if (cbOptHdr < (offsetof(IMAGE_OPTIONAL_HEADER, SizeOfImage) + sizeofmember(IMAGE_OPTIONAL_HEADER, SizeOfImage)))
{
goto lDone;
}

//
// The real check
//

baseAddr = dac_cast<PCODE>(pBase);
if ((pParam->ip < baseAddr) || (pParam->ip >= (baseAddr + VAL32(pNT->OptionalHeader.SizeOfImage))))
{
goto lDone;
}

pParam->fRet = TRUE;

lDone: ;
}
PAL_EXCEPT (EXCEPTION_EXECUTE_HANDLER)
{
}
PAL_ENDTRY
#endif // HOST_WINDOWS

return param.fRet;
}

namespace Clr
{
namespace Util
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/vm/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,17 @@ using std::min;

//-----------------------------------------------------------------------------------------------------------

#include "stdmacros.h"

#define POISONC ((UINT_PTR)((sizeof(int *) == 4)?0xCCCCCCCCL:0xCCCCCCCCCCCCCCCCLL))

#include "switches.h"
#include "eecontract.h"
#include "holder.h"
#include "classnames.h"
#include "util.hpp"
#include "corpriv.h"

#include <stdmacros.h>

#include <daccess.h>

typedef VPTR(class LoaderAllocator) PTR_LoaderAllocator;
Expand Down Expand Up @@ -210,7 +211,6 @@ namespace Loader
#include "cgensys.h"
#include "ceemain.h"
#include "hash.h"
#include "eecontract.h"
#include "pedecoder.h"
#include "sstring.h"
#include "slist.h"
Expand Down
9 changes: 5 additions & 4 deletions src/coreclr/vm/crst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ Volatile<LONG> g_ShutdownCrstUsageCount = 0;
//-----------------------------------------------------------------
// Initialize critical section
//-----------------------------------------------------------------
VOID CrstBase::InitWorker(INDEBUG_COMMA(CrstType crstType) CrstFlags flags)
void CrstBase::InitWorker(INDEBUG_COMMA(CrstType crstType) CrstFlags flags)
{
CONTRACTL {
THROWS;
WRAPPER(GC_TRIGGERS);
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
Comment thread
AaronRobinsonMSFT marked this conversation as resolved.

_ASSERTE((flags & CRST_INITIALIZED) == 0);
Expand Down
29 changes: 2 additions & 27 deletions src/coreclr/vm/crst.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,7 @@ class CrstBase
}

protected:

VOID InitWorker(INDEBUG_COMMA(CrstType crstType) CrstFlags flags);
void InitWorker(INDEBUG_COMMA(CrstType crstType) CrstFlags flags);

#ifdef _DEBUG
void DebugInit(CrstType crstType, CrstFlags flags);
Expand Down Expand Up @@ -453,7 +452,7 @@ typedef DPTR(Crst) PTR_Crst;
class CrstStatic : public CrstBase
{
public:
VOID Init(CrstType crstType, CrstFlags flags = CRST_DEFAULT)
void Init(CrstType crstType, CrstFlags flags = CRST_DEFAULT)
{
WRAPPER_NO_CONTRACT;

Expand All @@ -462,30 +461,6 @@ class CrstStatic : public CrstBase
// throw away the debug-only parameter in retail
InitWorker(INDEBUG_COMMA(crstType) flags);
}

bool InitNoThrow(CrstType crstType, CrstFlags flags = CRST_DEFAULT)
{
CONTRACTL {
NOTHROW;
} CONTRACTL_END;

_ASSERTE((flags & CRST_INITIALIZED) == 0);

bool fSuccess = false;

EX_TRY
{
// throw away the debug-only parameter in retail
InitWorker(INDEBUG_COMMA(crstType) flags);
fSuccess = true;
}
EX_CATCH
{
}
EX_END_CATCH

return fSuccess;
}
};

/* to be used as regular variable when a explicit call to Init method is needed */
Expand Down
Loading
Loading