Skip to content

Conversation

@nvborisenko
Copy link
Member

🔗 Related Issues

Contribues to #16719

💥 What does this PR do?

🔧 Implementation Notes

💡 Additional Considerations

🔄 Types of changes

  • Cleanup (formatting, renaming)
  • Bug fix (backwards compatible)
  • New feature (non-breaking change which adds functionality and tests!)
  • Breaking change (fix or feature that would cause existing functionality to change)

@nvborisenko nvborisenko marked this pull request as draft January 30, 2026 18:03
@selenium-ci selenium-ci added the C-dotnet .NET Bindings label Jan 30, 2026
@qodo-code-review
Copy link
Contributor

PR Type

Enhancement


Description

  • Refactor RemoteValue conversion to support multiple primitive types

  • Add comprehensive type conversion via generic ConvertTo<TResult>() method

  • Support conversion to arrays and generic collections with element type handling

  • Add 248 test cases covering all supported type conversions


File Walkthrough

Relevant files
Enhancement
RemoteValue.cs
Implement comprehensive type conversion system for RemoteValue

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs

  • Refactored implicit operators to use new ConvertTo() method
  • Implemented comprehensive generic conversion method using pattern
    matching
  • Added support for bool, short, ushort, int, uint, long, ulong, double,
    float conversions
  • Added support for array and generic list conversions with element type
    reflection
  • Added two private helper methods for array and generic list conversion
    logic
+78/-31 
Tests
RemoteValueConversionTest.cs
Add comprehensive RemoteValue conversion test coverage     

dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs

  • Created new test class with 15 test methods covering all conversion
    scenarios
  • Tests for nullable type conversions returning null values
  • Tests for primitive type conversions (bool, int16, uint16, int32,
    uint32, int64, uint64, double, float, string)
  • Tests for array and generic collection conversions (List, IEnumerable,
    IReadOnlyList, IReadOnlyCollection, IList, ICollection)
  • Tests for empty array and collection edge cases
+248/-0 

@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Denial of service

Description: Converting ArrayRemoteValue to arrays/lists performs unbounded allocations based on
remoteValues size (ToList(), Array.CreateInstance, and list growth), which could allow a
remote endpoint to trigger excessive memory usage (DoS) by returning extremely large
arrays.
RemoteValue.cs [112-149]

Referred Code
private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
{
    if (remoteValues is null)
    {
        return (TResult)(object)Array.CreateInstance(elementType, 0);
    }

    var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
    var items = remoteValues.ToList();
    var array = Array.CreateInstance(elementType, items.Count);

    for (int i = 0; i < items.Count; i++)
    {
        var convertedItem = convertMethod.Invoke(items[i], null);
        array.SetValue(convertedItem, i);
    }

    return (TResult)(object)array;
}

private static TResult ConvertRemoteValuesToGenericList<TResult>(IEnumerable<RemoteValue>? remoteValues, Type listType)


 ... (clipped 17 lines)
Ticket Compliance
🟡
🎫 #16719
🟢 Add implicit conversion support from Script.RemoteValue to .NET well-known types to enable
patterns like await bidi.Script.EvaluateAsync(...).
Extend the set of supported .NET types for converting RemoteValue (beyond the few
primitive examples already present).
Confirm EvaluateAsync (and any other BiDi Script APIs returning RemoteValue) actually uses
these implicit operators / ConvertTo() end-to-end in real BiDi sessions.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing edge handling: Numeric and reflective conversions can throw runtime exceptions (e.g., overflow or
reflection Invoke failures) without being caught/wrapped to provide actionable context or
graceful degradation.

Referred Code
(NumberRemoteValue n, Type t) when t == typeof(short)
    => (TResult)(object)Convert.ToInt16(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(ushort)
    => (TResult)(object)Convert.ToUInt16(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(int)
    => (TResult)(object)Convert.ToInt32(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(uint)
    => (TResult)(object)Convert.ToUInt32(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(long)
    => (TResult)(object)Convert.ToInt64(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(ulong)
    => (TResult)(object)Convert.ToUInt64(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(double)
    => (TResult)(object)n.Value,
(NumberRemoteValue n, Type t) when t == typeof(float)
    => (TResult)(object)Convert.ToSingle(n.Value),
(StringRemoteValue s, Type t) when t == typeof(string)
    => (TResult)(object)s.Value,
(ArrayRemoteValue a, Type t) when t.IsArray
    => ConvertRemoteValuesToArray<TResult>(a.Value, t.GetElementType()!),
(ArrayRemoteValue a, Type t) when t.IsGenericType && t.IsAssignableFrom(typeof(List<>).MakeGenericType(t.GetGenericArguments()[0]))


 ... (clipped 40 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unvalidated value ranges: Converting externally-sourced numeric RemoteValue data via Convert.ToInt16/ToUInt64/...
lacks explicit range/NaN/Infinity validation and may throw or behave unexpectedly
depending on upstream inputs.

Referred Code
(NumberRemoteValue n, Type t) when t == typeof(short)
    => (TResult)(object)Convert.ToInt16(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(ushort)
    => (TResult)(object)Convert.ToUInt16(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(int)
    => (TResult)(object)Convert.ToInt32(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(uint)
    => (TResult)(object)Convert.ToUInt32(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(long)
    => (TResult)(object)Convert.ToInt64(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(ulong)
    => (TResult)(object)Convert.ToUInt64(n.Value),
(NumberRemoteValue n, Type t) when t == typeof(double)
    => (TResult)(object)n.Value,
(NumberRemoteValue n, Type t) when t == typeof(float)
    => (TResult)(object)Convert.ToSingle(n.Value),

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@nvborisenko
Copy link
Member Author

@RenderMichael please quickly take a look whether it is good direction.

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Reflection-based conversion creates performance bottleneck

The current implementation uses reflection for converting elements within arrays
and lists, which can be slow. To improve performance, consider caching the
reflected methods to avoid repeated lookups.

Examples:

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [112-130]
    private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
    {
        if (remoteValues is null)
        {
            return (TResult)(object)Array.CreateInstance(elementType, 0);
        }

        var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
        var items = remoteValues.ToList();
        var array = Array.CreateInstance(elementType, items.Count);

 ... (clipped 9 lines)
dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [132-148]
    private static TResult ConvertRemoteValuesToGenericList<TResult>(IEnumerable<RemoteValue>? remoteValues, Type listType)
    {
        var elementType = listType.GetGenericArguments()[0];
        var list = (System.Collections.IList)Activator.CreateInstance(listType)!;

        if (remoteValues is not null)
        {
            var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);

            foreach (var item in remoteValues)

 ... (clipped 7 lines)

Solution Walkthrough:

Before:

private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
{
    if (remoteValues is null)
    {
        return (TResult)(object)Array.CreateInstance(elementType, 0);
    }

    var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
    var items = remoteValues.ToList();
    var array = Array.CreateInstance(elementType, items.Count);

    for (int i = 0; i < items.Count; i++)
    {
        var convertedItem = convertMethod.Invoke(items[i], null);
        array.SetValue(convertedItem, i);
    }

    return (TResult)(object)array;
}

After:

private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, System.Reflection.MethodInfo> _converterCache = new();

private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
{
    if (remoteValues is null)
    {
        return (TResult)(object)Array.CreateInstance(elementType, 0);
    }

    var convertMethod = _converterCache.GetOrAdd(elementType, 
        type => typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(type));
    
    var items = remoteValues.ToList();
    var array = Array.CreateInstance(elementType, items.Count);

    for (int i = 0; i < items.Count; i++)
    {
        var convertedItem = convertMethod.Invoke(items[i], null);
        array.SetValue(convertedItem, i);
    }

    return (TResult)(object)array;
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a significant performance issue due to reflection in collection conversions, which is a valid and important concern for a library feature.

Medium
General
Generalize null conversion for all types

Generalize the NullRemoteValue conversion to support all nullable and reference
types by replacing the hardcoded list with a check for !t.IsValueType or if the
type is Nullable.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [74-85]

-(NullRemoteValue _, Type t) when
-    t == typeof(bool?) ||
-    t == typeof(short?) ||
-    t == typeof(ushort?) ||
-    t == typeof(int?) ||
-    t == typeof(uint?) ||
-    t == typeof(long?) ||
-    t == typeof(ulong?) ||
-    t == typeof(double?) ||
-    t == typeof(float?) ||
-    t == typeof(string)
+(NullRemoteValue _, Type t) when !t.IsValueType || Nullable.GetUnderlyingType(t) is not null
     => default,
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the hardcoded list of types for null conversion is brittle and replaces it with a more robust, generic check, improving maintainability.

Medium
Improve generic list conversion logic

Refine the ArrayRemoteValue conversion logic to distinguish between
interfaces/abstract classes and concrete list types, creating a List for the
former and an instance of the specific type for the latter.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [106-107]

-(ArrayRemoteValue a, Type t) when t.IsGenericType && t.IsAssignableFrom(typeof(List<>).MakeGenericType(t.GetGenericArguments()[0]))
+(ArrayRemoteValue a, Type t) when t.IsGenericType && (t.IsInterface || t.IsAbstract) && t.IsAssignableFrom(typeof(List<>).MakeGenericType(t.GetGenericArguments()[0]))
     => ConvertRemoteValuesToGenericList<TResult>(a.Value, typeof(List<>).MakeGenericType(t.GetGenericArguments()[0])),
+(ArrayRemoteValue a, Type t) when t.IsGenericType && !t.IsAbstract && typeof(System.Collections.IList).IsAssignableFrom(t)
+    => ConvertRemoteValuesToGenericList<TResult>(a.Value, t),
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: This is a good suggestion that refines the new collection conversion logic, making it more flexible by handling concrete list types differently from interfaces, which improves the feature's versatility.

Low
Use a smaller tolerance for float comparison

In the CanConvertToFloat test, reduce the assertion tolerance from 0.1f to a
much smaller value like 1e-6f to more accurately verify the precision of the
float conversion.

dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs [161-173]

 [Test]
 public void CanConvertToFloat()
 {
     NumberRemoteValue arg = new(5.1);
 
     AssertValue(arg.ConvertTo<float>());
     AssertValue((float)arg);
 
     static void AssertValue(float value)
     {
-        Assert.That(value, Is.EqualTo(5.1f).Within(0.1f));
+        Assert.That(value, Is.EqualTo(5.1f).Within(1e-6f));
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that the test tolerance for float conversion is too loose and proposes a stricter value, which improves the quality and reliability of the new test suite.

Low
Learned
best practice
Cache reflection-based conversions

Cache the constructed generic ConvertTo delegate per elementType (or avoid
reflection entirely when possible) to prevent repeated MethodInfo.Invoke calls
and reduce runtime overhead.

dotnet/src/webdriver/BiDi/Script/RemoteValue.cs [112-130]

+private static readonly Dictionary<Type, Func<RemoteValue, object?>> ConvertToCache = new();
+
 private static TResult ConvertRemoteValuesToArray<TResult>(IEnumerable<RemoteValue>? remoteValues, Type elementType)
 {
     if (remoteValues is null)
     {
         return (TResult)(object)Array.CreateInstance(elementType, 0);
     }
 
-    var convertMethod = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
+    if (!ConvertToCache.TryGetValue(elementType, out var converter))
+    {
+        var method = typeof(RemoteValue).GetMethod(nameof(ConvertTo))!.MakeGenericMethod(elementType);
+        converter = rv => method.Invoke(rv, null);
+        ConvertToCache[elementType] = converter;
+    }
+
     var items = remoteValues.ToList();
     var array = Array.CreateInstance(elementType, items.Count);
 
     for (int i = 0; i < items.Count; i++)
     {
-        var convertedItem = convertMethod.Invoke(items[i], null);
-        array.SetValue(convertedItem, i);
+        array.SetValue(converter(items[i]), i);
     }
 
     return (TResult)(object)array;
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Avoid per-item reflection (MethodInfo.Invoke) in hot-path conversions; prefer type-safe generic code or cached delegates for performance and reliability.

Low
Ensure tests are discoverable

Change the test class to public (and consider naming it
RemoteValueConversionTests) so NUnit test discovery is robust across tooling and
configurations.

dotnet/test/common/BiDi/Script/RemoteValueConversionTest.cs [25-248]

-internal class RemoteValueConversionTest
+public class RemoteValueConversionTests
 {
     [Test]
     public void CanConvertToNullable()
     {
         NullRemoteValue arg = new();
 
         AssertValue(arg.ConvertTo<bool?>());
         AssertValue(arg.ConvertTo<short?>());
         AssertValue(arg.ConvertTo<ushort?>());
         AssertValue(arg.ConvertTo<int?>());
         AssertValue(arg.ConvertTo<uint?>());
         AssertValue(arg.ConvertTo<long?>());
         AssertValue(arg.ConvertTo<ulong?>());
         AssertValue(arg.ConvertTo<double?>());
         AssertValue(arg.ConvertTo<float?>());
         AssertValue(arg.ConvertTo<string>());
 
         static void AssertValue<T>(T value)
         {
             Assert.That(value, Is.Null);
         }
     }
     ...
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Make test fixtures publicly discoverable and follow conventional naming to ensure consistent test discovery across runners.

Low
  • More

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants