From 3a2460aed9beb658f241563a992a55c2e7007fad Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 29 Jul 2026 14:15:39 +0300 Subject: [PATCH] Declare the object converter's types and convert enums natively An IObjectConverter registered without declaring the CLR types it handles can be handed anything, so the engine has to assume every wrapped CLR member read might reach it and disables the compiled interop member-read lane engine-wide. JintObjectConverter handles a closed set, so declare it: matching is by assignability, which keeps IUser covering every implementation. The converter is still offered every value - the declaration only lets the engine keep the fast lane for members whose declared type could never produce a handled value, and it errs towards claiming (a member typed `object` is always claimed). The Enum branch is dropped in favour of Options.Interop.EnumConversion = EnumConversionMode.String, which Jint documents as the member name "as produced by object.ToString()", including the comma-separated combination for a [Flags] value and the numeric value rendered as a string for a value with no name - verbatim what the branch did. The write direction keeps accepting both the name and the number. Handling enums natively rather than through the converter also keeps one more declared type off the list, so more members stay on the fast lane. Pinned by Should_convert_enum_to_name, Should_convert_flags_enum_to_names and Should_convert_enum_member_of_wrapped_object_to_name, which pass before and after. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016uV6H9cTntzsoKiaJRBn4f --- .../Scripting/Internal/JintObjectConverter.cs | 29 +++++++++++++++---- .../Scripting/JintScriptEngine.cs | 3 +- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs index 5696a48db..e9dfd69ee 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/JintObjectConverter.cs @@ -21,6 +21,29 @@ namespace Squidex.Domain.Apps.Core.Scripting.Internal; public sealed class JintObjectConverter : IObjectConverter { + /// + /// The CLR types this converter answers for, declared at registration so the engine can keep its + /// compiled interop member-read lane for members whose declared type can never reach this converter. + /// + /// + /// Matching is by assignability, so covers every implementation. Registering the + /// converter without this set makes every wrapped CLR member read in the engine take the slow lane. + /// Enums are not listed: they are handled natively through + /// . + /// + public static readonly Type[] HandledTypes = + [ + typeof(IUser), + typeof(ClaimsPrincipal), + typeof(ScriptVars), + typeof(JsonValue), + typeof(DomainId), + typeof(Guid), + typeof(Instant), + typeof(Status), + typeof(ContentData), + ]; + public static readonly JintObjectConverter Instance = new JintObjectConverter(); private JintObjectConverter() @@ -31,12 +54,6 @@ public sealed class JintObjectConverter : IObjectConverter { result = null!; - if (value is Enum) - { - result = value.ToString(); - return true; - } - switch (value) { case IUser user: diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs index 428182cf7..faf8c20cc 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs @@ -143,8 +143,9 @@ public sealed class JintScriptEngine(IMemoryCache cache, IOptions { - engineOptions.AddObjectConverter(JintObjectConverter.Instance); + engineOptions.AddObjectConverter(JintObjectConverter.Instance, JintObjectConverter.HandledTypes); engineOptions.AllowClrWrite(!options.Readonly); + engineOptions.Interop.EnumConversion = EnumConversionMode.String; engineOptions.SetTypeConverter(engine => new CustomClrConverter(engine)); engineOptions.SetReferencesResolver(NullPropagation.Instance, NullPropagation.Interests); engineOptions.Strict();