Stop attaching Array.prototype to dictionary-like objects in JavaScript expressions - #7890
Merged
sfmskywalker merged 2 commits intoSep 14, 2026
Conversation
Contributor
|
PR author is not in the allowed authors list. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR corrects how CLR objects are wrapped/exposed inside Elsa’s Jint-based JavaScript evaluator by removing a custom WrapObjectDelegate that incorrectly attached Array.prototype to dictionary-like objects (e.g., ExpandoObject-backed variables/args), and by adding integration coverage to prevent regressions.
Changes:
- Removed the custom Jint object-wrap handler so Jint’s built-in array-likeness logic governs whether
Array.prototypeis attached. - Marked
ObjectArrayHelperas[Obsolete]since the evaluator no longer uses it. - Added integration tests asserting dictionary-like objects remain plain objects while array-like CLR collections still get array prototype behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/integration/Elsa.JavaScript.IntegrationTests/ObjectWrappingTests.cs | Adds integration tests validating prototype behavior for variables, dictionary-like objects, and array-like CLR collections. |
| src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs | Removes the custom wrap-object handler to rely on Jint’s default object wrapping behavior. |
| src/modules/Elsa.Expressions.JavaScript/Helpers/ObjectArrayHelper.cs | Marks the helper obsolete now that array-likeness decisions are delegated to Jint. |
lahma
force-pushed
the
fix/js-object-wrapper-array-prototype
branch
from
August 8, 2026 11:53
df85bc0 to
4959eab
Compare
Contributor
Author
|
Rebased onto current main (bd903f6). No changes beyond the rebase; |
…jects The custom `WrapObjectDelegate` installed by `JintJavaScriptEvaluator` duplicated what Jint already does, and got it wrong in two ways. Jint's default wrap handler is `ObjectWrapper.Create(engine, target, type)`, and `ObjectWrapper` attaches `Array.prototype` to array-like wrappers by itself when `Options.Interop.AttachArrayPrototype` is enabled (the default). Jint's own array-likeness test deliberately excludes dictionary-like types, including string-keyed generic dictionaries. The handler we installed instead: * Called `ObjectWrapper.Create(engine, target)`, dropping the declared `type` argument, so members were resolved against the runtime type rather than the declared one. * Used `ObjectArrayHelper.DetermineIfObjectIsArrayLikeClrCollection`, which only excludes the non-generic `IDictionary`. `ExpandoObject` does not implement that interface, so it came out array-like. Both the `variables` container and the `args` container are `ExpandoObject` instances, which meant `Object.getPrototypeOf(variables) === Array.prototype` was true and `variables.map`, `variables.filter`, `variables.reduce` and friends were all visible on them, with `variables.length` reporting `0` instead of `undefined`. Removing the handler restores Jint's default, which handles every case the custom one was written for: `List<T>`, `T[]`, `HashSet<T>`, `ImmutableArray<T>`, `Queue<T>` and `Stack<T>` all still get `Array.prototype`, while dictionaries and `ExpandoObject` no longer do. `ObjectArrayHelper` is public, so it is marked obsolete rather than deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179sA2T7HuRfRfSc2JirFik
lahma
force-pushed
the
fix/js-object-wrapper-array-prototype
branch
from
August 18, 2026 16:54
4959eab to
26bbb02
Compare
`new ExpandoObject() as IDictionary<string, object>` reads as a conversion that might fail and gives the variable a nullable declared type, when `ExpandoObject` implements the interface unconditionally. A direct cast states that, and matches the BCL's `IDictionary<string, object?>` annotation exactly so the value type argument lines up too. The two other `as IDictionary<string, object>` uses in this test project (JintJavaScriptFunctionBehaviorTests) are deliberately left alone: there the operand is the untyped result of a script evaluation, so the `as` is a genuine type test paired with `Assert.NotNull`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179sA2T7HuRfRfSc2JirFik
lahma
force-pushed
the
fix/js-object-wrapper-array-prototype
branch
from
August 18, 2026 17:09
26bbb02 to
d5fa4d4
Compare
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.
Problem
JintJavaScriptEvaluatorinstalls a customWrapObjectDelegate:This duplicates what Jint already does, and gets it wrong in two ways.
Jint's default handler is
ObjectWrapper.Create(engine, target, type), andObjectWrapperattachesArray.prototypeto array-like wrappers by itself wheneverOptions.Interop.AttachArrayPrototypeis enabled — which it is by default. Jint's own array-likeness test deliberately treats dictionary-like types, including string-keyed generic dictionaries, as plain objects.The handler above:
typeargument, so wrapper members are resolved against the runtime type rather than the declared one;ObjectArrayHelper.DetermineIfObjectIsArrayLikeClrCollection, which only excludes the non-genericIDictionary.ExpandoObjectdoes not implement that interface — it implementsIDictionary<string, object>andICollection<KeyValuePair<string, object>>— so it comes out array-like.Both the
variablescontainer (ConfigureEngineWithVariables) and theargscontainer (JavaScriptExpressionHandler) areExpandoObjectinstances. The observable result today:Fix
Remove the handler. Jint's default covers every case the custom one was written for — verified against a plain engine:
Array.prototypestring[],List<T>,HashSet<T>,ImmutableArray<T>,ImmutableList<T>,Queue<T>,Stack<T>Dictionary<string, object>,ExpandoObject, a bareIEnumerable<T>iteratorObjectArrayHelperis public, so it is marked[Obsolete]rather than deleted.Tests
New
ObjectWrappingTestscovers both directions: thevariablescontainer and dictionary-like objects are plain objects, and array-like CLR collections still exposeArray.prototypeand still supportmap/reducewhere the collection is indexable.Elsa.JavaScript.IntegrationTests73 → 80 passing,Elsa.Workflows.IntegrationTests270 passing, both before and after.Note for reviewers:
HashSet<T>getsArray.prototypebut has no integer indexer, soset.map(...)yields empty entries. That is unchanged by this PR — it behaves identically with and without the custom handler — so the test asserts prototype presence for sets and functional iteration only for indexable collections.🤖 Generated with Claude Code
https://claude.ai/code/session_0179sA2T7HuRfRfSc2JirFik