Gate host-boundary constraint checks on active evaluation and harden coverage#2714
Merged
Merged
Conversation
Review fixes for the boundary-check mechanism: - Only check while script evaluation is active and not in debug mode: constraint state is meaningless outside an Execute/Invoke window (the time constraint's CTS is re-armed after every run and keeps counting, and a token cancelled during teardown must not make host-side C# reads of wrapped objects throw), and a paused debugger would otherwise trip expired timeouts on every watch-evaluation interop read. - Check only after the host call returns, never on entry: an entry check would block host cleanup calls (e.g. inside a JS finally block) once cancellation is pending. - Convert awaitable delegate results to a promise before the check so an in-flight Task is never dropped unobserved. - Move the constructor-lane check from MethodDescriptor.Call (where operator-overload resolution's catch-all would launder constraint exceptions into TargetInvocationException) to TypeReference.Construct, now also covering the user-factory and Activator creation branches. - Cover the dictionary interop lane (TryGetDictionaryValue / TrySetDictionaryValue), the wrapped-IEnumerable iterator lane and the Options.Interop.MemberAccessor callback. - Drop the redundant GetterFunction/SetterFunction checks (both consumers either re-check in ReflectionAccessor or run no host code). - Replace the CTS-timer-dependent timeout test with a deterministic lone-call test isolating the post-invoke check, add host-side/debug-mode regression tests and dictionary/iterator lane tests, dedupe the test scaffolding, and deduplicate the design rationale docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lahma
enabled auto-merge (squash)
July 20, 2026 19:25
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up hardening for #2713 (merged before this review pass landed on the branch). An adversarial multi-agent review of the boundary-check mechanism confirmed several edge-case regressions and coverage gaps in the merged version; this PR fixes them.
Regressions fixed
CancellationConstraintstate survives execution — so after Re-check amortized constraints at interpreter/host-code boundaries #2713, reading a wrapped object's members from plain C# after execution could throwTimeoutException/ExecutionCanceledExceptionwith no script running. The boundary check is now gated on an active evaluation context.finallyblock (release-a-lock pattern) threw before the delegate body ran, skipping cleanup that executed pre-Re-check amortized constraints at interpreter/host-code boundaries #2713. Checks now run only after the host call returns — the post-return check alone preserves the ≤ 1-host-call detection bound.TimeoutIntervalwould deterministically fail every interop read in watch/conditional-breakpoint expressions. Debug-mode engines now keep the pre-existing amortized-countdown-only behavior.Task: the check betweenDynamicInvokeand promise conversion could discard a still-runningTaskreturned by an async host delegate, surfacing its eventual fault asTaskScheduler.UnobservedTaskException. Awaitable results now reachConvertAwaitableToPromise(attaching the continuation) before the check.MethodDescriptor.Callsat inside operator-overload resolution's catch-all, which rethrows asTargetInvocationException— embedder sandbox handlers catchingExecutionCanceledException/TimeoutExceptionwould miss. The constructor-lane check moved toTypeReference.Construct, where it now also covers theOptions.Interop.CreateTypeReferenceObjectfactory andActivatorvalue-type branches.Coverage extended
The #2707 deferred-detection gap also existed in lanes #2713 did not instrument; now covered:
TryGetDictionaryValue/TrySetDictionaryValueon wrappedIDictionary<,>— e.g. a service-backed dictionary with slow lookups)IEnumerableiteration (MoveNextin for-of)Options.Interop.MemberAccessorcallbackAlso removed the redundant
GetterFunction/SetterFunctionchecks (both consumers either re-check insideReflectionAccessoror run no host code).Still intentionally uninstrumented: built-in
ClrFunctiondispatch (per-call cost on hot built-ins; built-ins self-check viaEngine.ConstraintCheckInterval), operator-overload resolution, and user-supplied converter/factory callbacks.Tests
Full
Jint.Tests,Jint.Tests.PublicInterface,Jint.Tests.CommonScriptsand Test262 suites pass on net10.0 and net472.InteropLambdaBenchmark.ForLoopA/B (including the newly covered Dictionary lane) shows no regression — all rows within variance, allocations byte-identical.🤖 Generated with Claude Code