Przeglądaj źródła

Disallow unsafe floating point conversion caching (#1456)

Marko Lahma 2 lat temu
rodzic
commit
c4ff805167

+ 21 - 0
Jint.Tests/Runtime/InteropTests.cs

@@ -2473,6 +2473,27 @@ namespace Jint.Tests.Runtime
             Assert.Equal("float-val", engine.Evaluate("a.testFunc(12.3);").AsString());
         }
 
+        [Fact]
+        public void TypeConversionWithTemporaryInvalidValuesShouldNotCache()
+        {
+            var engine = new Engine(options => options.AllowClr());
+            engine.SetValue("IntValueInput", TypeReference.CreateTypeReference(engine, typeof(IntValueInput)));
+            var ex = Assert.Throws<JavaScriptException>(() => engine.Evaluate("new IntValueInput().testFunc(NaN);").AsString());
+            Assert.Equal("No public methods with the specified arguments were found.", ex.Message);
+
+            Assert.Equal(123, engine.Evaluate("new IntValueInput().testFunc(123);").AsNumber());
+
+            ex = Assert.Throws<JavaScriptException>(() => engine.Evaluate("new IntValueInput().testFunc(12.3);").AsNumber());
+            Assert.Equal("No public methods with the specified arguments were found.", ex.Message);
+
+            Assert.Equal(123, engine.Evaluate("new IntValueInput().testFunc(123);").AsNumber());
+        }
+
+        public class IntValueInput
+        {
+            public int TestFunc(int value) => value;
+        }
+
         public class TestItem
         {
             public double Cost { get; set; }

+ 7 - 0
Jint/Extensions/ReflectionExtensions.cs

@@ -131,6 +131,13 @@ namespace Jint.Extensions
             JsValue value,
             [NotNullWhen(true)] out object? converted)
         {
+            if (value.IsInteger() && (memberType == typeof(int) || memberType == typeof(long)))
+            {
+                // safe and doesn't require configuration
+                converted = value.AsInteger();
+                return true;
+            }
+
             if (memberType == typeof(bool) && (valueCoercionType & ValueCoercionType.Boolean) != 0)
             {
                 converted = TypeConverter.ToBoolean(value);

+ 16 - 3
Jint/Runtime/Interop/DefaultTypeConverter.cs

@@ -39,7 +39,7 @@ namespace Jint.Runtime.Interop
 
         public virtual object? Convert(object? value, Type type, IFormatProvider formatProvider)
         {
-            if (value == null)
+            if (value is null)
             {
                 if (TypeConverter.TypeIsNullable(type))
                 {
@@ -81,6 +81,19 @@ namespace Jint.Runtime.Interop
             }
 
             var valueType = value.GetType();
+
+            if (valueType == typeof(double) || valueType == typeof(float) || valueType == typeof(decimal))
+            {
+                // conversion can be dangerous
+                var doubleValue = (double) value;
+                if (!TypeConverter.IsIntegralNumber(doubleValue)
+                    && (type == typeof(long) || type == typeof(int) || type == typeof(short) || type == typeof(byte) || type == typeof(ulong) || type == typeof(uint) || type == typeof(ushort) || type == typeof(sbyte)))
+                {
+                    // this is not safe
+                    ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(value) , "Cannot convert floating point number with decimals to integral type");
+                }
+            }
+
             // is the javascript value an ICallable instance ?
             if (valueType == iCallableType)
             {
@@ -323,7 +336,7 @@ namespace Jint.Runtime.Interop
             var key = new TypeConversionKey(value?.GetType() ?? typeof(void), type);
 
             // string conversion is not stable, "filter" -> int is invalid, "0" -> int is valid
-            var canConvert = value is string || _knownConversions.GetOrAdd(key, _ =>
+            var canTryConvert = value is string || _knownConversions.GetOrAdd(key, _ =>
             {
                 try
                 {
@@ -336,7 +349,7 @@ namespace Jint.Runtime.Interop
                 }
             });
 
-            if (canConvert)
+            if (canTryConvert)
             {
                 try
                 {