Fix COM reentrancy contract violations and re-enable the ExtensionPoints test#126571
Conversation
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/68c1bb22-8f29-41a2-86b8-b039f3aa63bc Co-authored-by: AaronRobinsonMSFT <30635565+AaronRobinsonMSFT@users.noreply.github.com>
|
Tagging subscribers to this area: @dotnet/interop-contrib |
…r and update related calls
There was a problem hiding this comment.
Pull request overview
This PR adjusts CoreCLR COM interop contract handling to accommodate managed re-entrancy (e.g., managed IMallocSpy) during marshaling cleanup and related COM-call paths.
Changes:
- Update
OleVariant::ClearLPSTRArray/ClearLPWSTRArraycleanup paths to tolerateCoTaskMemFreere-entrancy via a contract-violation annotation. - Simplify
CheckParentComVisibilityAPI by removing an unusedBOOL fForIDispatchparameter and updating call sites. - Add a
ThrowsViolationcontract-violation scope inComPreStubWorkeraround the managed exception dispatcher / unwind handler region.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/stubhelpers.cpp | Update call site to new CheckParentComVisibility() signature. |
| src/coreclr/vm/stdinterfaces.cpp | Update IDispatch-related call sites to new CheckParentComVisibility() signature. |
| src/coreclr/vm/olevariant.cpp | Annotate LPSTR/LPWSTR array cleanup to allow managed re-entrancy during CoTaskMemFree. |
| src/coreclr/vm/comcallablewrapper.h | Remove unused fForIDispatch parameter from CheckParentComVisibility* APIs. |
| src/coreclr/vm/comcallablewrapper.cpp | Add contract-violation scope in ComPreStubWorker; update visibility checks to new signature. |
Comments suppressed due to low confidence (1)
src/coreclr/vm/comcallablewrapper.cpp:324
BEGIN_CONTRACT_VIOLATION(ThrowsViolation)introduces a scope that must always reachEND_CONTRACT_VIOLATION. UnderFEATURE_INTERPRETER, the earlyreturn pInterpreterTarget;exits the function without callingEND_CONTRACT_VIOLATION, leaving the violation state active on the thread (and skipping the explicit leave call). Restructure so all returns afterBEGIN_CONTRACT_VIOLATIONleave the scope (e.g., move the interpreter fast-path beforeBEGIN_CONTRACT_VIOLATION, or ensure it executesEND_CONTRACT_VIOLATIONbefore returning).
BEGIN_CONTRACT_VIOLATION(ThrowsViolation);
INSTALL_MANAGED_EXCEPTION_DISPATCHER;
INSTALL_UNWIND_AND_CONTINUE_HANDLER;
#ifdef FEATURE_INTERPRETER
PCODE pInterpreterTarget = pEntryThunk->GetInterpreterTarget();
if (pInterpreterTarget != (PCODE)0)
{
t_MostRecentUMEntryThunkData = pEntryThunk;
return pInterpreterTarget;
}
This comment has been minimized.
This comment has been minimized.
|
@MichalStrehovsky This PR should fix the contract violations you found. |
Sounds good. We'll only be able to test this once #126517 merges since right now the test doesn't run at all. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/coreclr/vm/comcallablewrapper.cpp:324
ComPreStubWorkernow entersBEGIN_CONTRACT_VIOLATION(ThrowsViolation)and installs the managed exception dispatcher / unwind handler, but theFEATURE_INTERPRETERfast path returns early (return pInterpreterTarget;) beforeUNINSTALL_*andEND_CONTRACT_VIOLATION.ContractViolationHolderdoes not auto-leave on scope exit, so this leaves the violation mask (and the try/catch dispatcher scope) unbalanced for the rest of the thread’s execution. Consider moving the interpreter-target early-return before theBEGIN_CONTRACT_VIOLATION/INSTALL_*sequence, or refactor to a single-exit cleanup path that always uninstalls and ends the contract violation before returning.
BEGIN_CONTRACT_VIOLATION(ThrowsViolation);
INSTALL_MANAGED_EXCEPTION_DISPATCHER;
INSTALL_UNWIND_AND_CONTINUE_HANDLER;
#ifdef FEATURE_INTERPRETER
PCODE pInterpreterTarget = pEntryThunk->GetInterpreterTarget();
if (pInterpreterTarget != (PCODE)0)
{
t_MostRecentUMEntryThunkData = pEntryThunk;
return pInterpreterTarget;
}
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/dda7a23f-910f-4f7f-b3f8-4b21b06d6a14 Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/76569a90-7ab4-4a60-ab3d-c1795c141dd9 Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
|
Note This review was generated by Copilot. 🤖 Copilot Code Review — PR #126571Holistic AssessmentMotivation: This PR addresses contract annotation correctness in COM interop code. It fixes three distinct issues: (1) Approach: The approach is appropriate for each change: Summary: ✅ LGTM. All changes are correct, well-motivated, and consistent with established codebase patterns. The dead Detailed Findings✅ Contract Correctness —
|
|
@copilot Now that #126517 has been checked in (see #126571 (comment)), please update |
There was a problem hiding this comment.
Pull request overview
Fixes CoreCLR COM interop contract violations in ExtensionPoints-related paths where native COM cleanup/allocation can legitimately re-enter managed code (e.g., via managed IMallocSpy), and simplifies COM visibility helper APIs.
Changes:
- Annotate
OleVariant::ClearLPSTRArray/ClearLPWSTRArrayCoTaskMem cleanup paths with a contract violation for runtime reentrancy while keeping the functionsNOTHROW. - Wrap
ComPreStubWorker’s exception dispatcher/unwind setup in aBEGIN_CONTRACT_VIOLATION(ThrowsViolation)scope and align its outer contract toSTATIC_CONTRACT_NOTHROW. - Remove the unused
fForIDispatchparameter and eliminate the redundantCheckParentComVisibilityNoThrowhelper, updating call sites accordingly.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/stubhelpers.cpp | Updates COM visibility validation call to the new CheckParentComVisibility() signature. |
| src/coreclr/vm/stdinterfaces.cpp | Updates IDispatch-related paths to use the new CheckParentComVisibility() signature. |
| src/coreclr/vm/olevariant.cpp | Adds contract-violation annotation around LPSTR/LPWSTR CoTaskMem cleanup to account for managed reentrancy. |
| src/coreclr/vm/comcallablewrapper.h | Simplifies COM visibility helper declarations by removing unused parameter and redundant helper. |
| src/coreclr/vm/comcallablewrapper.cpp | Adjusts ComPreStubWorker contracts/violation scope and simplifies COM visibility helper implementation and call sites. |
Copilot's findings
Comments suppressed due to low confidence (1)
src/coreclr/vm/olevariant.cpp:1749
- Same as above: the unconditional
PERMANENT_CONTRACT_VIOLATION(ThrowsViolation, ...)suppresses throws-contract checks even when noCoTaskMemFreeis executed (e.g.,cElements == 0or all entries are null). Please consider narrowing the violation scope/condition to theCoTaskMemFreepath to avoid masking unrelated contract issues.
GCX_PREEMP();
LPSTR *pOle = (LPSTR *) oleArray;
LPSTR *pOleEnd = pOle + cElements;
PERMANENT_CONTRACT_VIOLATION(ThrowsViolation, ReasonRuntimeReentrancy); // IMallocSpy in managed
while (pOle < pOleEnd)
{
- Files reviewed: 5/5 changed files
- Comments generated: 1
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6e3f0d63-765e-4f6a-bdda-7ebfdd27e4ec Co-authored-by: AaronRobinsonMSFT <30635565+AaronRobinsonMSFT@users.noreply.github.com>
Updated in e782db0: |
Note
This PR description was updated with GitHub Copilot assistance.
This PR fixes the
Interop/COM/ExtensionPointsfailure by correcting CoreCLR COM interop contract handling for paths that can legitimately re-enter managed code during COM allocation hooks, and it includes a small cleanup of the related COM visibility helpers. Now that the test infrastructure change has merged, it also re-enables theExtensionPointsIMallocSpy test.Changes
OleVariant::ClearLPSTRArrayandOleVariant::ClearLPWSTRArrayasNOTHROW, but mark theCoTaskMemFreecleanup paths with a permanentThrowsViolationfor runtime reentrancy, sinceCoTaskMemFreecan invoke a managedIMallocSpyimplementation.ComPreStubWorkersetup around the managed exception dispatcher and unwind handler in aBEGIN_CONTRACT_VIOLATION(ThrowsViolation)/END_CONTRACT_VIOLATIONblock.ComPreStubWorkertoSTATIC_CONTRACT_NOTHROWso it matches the intended HRESULT-returning behavior outside the explicit violation scope.fForIDispatchparameter fromCheckParentComVisibility.CheckParentComVisibilityNoThrowhelper and useHasInvisibleParent()directly at the remaining visibility check.src/tests/Interop/COM/ExtensionPoints/ExtensionPoints.csby removing the temporaryActiveIssueskip onValidate_Managed_IMallocSpy.Why this addresses the failure
The failing test exercises COM extension points that can callback into managed code during allocation cleanup and stub initialization. Those paths were operating under overly strict contracts, which made valid reentrant behavior show up as contract violations. This change narrows the violation handling to the specific reentrant regions while keeping the surrounding HRESULT-returning contracts intact. The visibility-helper refactors are supporting cleanups with no intended behavioral change, and re-enabling the test ensures the original failure scenario is covered again now that the test can run.