Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various Interop fixes #1063

Merged
merged 5 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions Jint.Tests/Runtime/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,25 @@ public void IteratorsShouldHaveIteratorSymbol()
_engine.Execute("assert(!!['hello'].values()[Symbol.iterator])");
_engine.Execute("assert(!!new Map([['hello', 'world']]).keys()[Symbol.iterator])");
}



[Fact]
public void ArraySortDoesNotCrashInDebugMode()
{
var engine = new Engine(o =>
{
o.DebugMode(true);
});
engine.SetValue("equal", new Action<object, object>(Assert.Equal));

const string code = @"
var items = [5,2,4,1];
items.sort((a,b) => a - b);
equal('1,2,4,5', items.join());
";

engine.Execute(code);
}
}
}
25 changes: 25 additions & 0 deletions Jint.Tests/Runtime/ConstTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,30 @@ public void ConstDestructuring()
}
");
}

[Fact]
public void DestructuringWithFunctionArgReferenceInStrictMode()
{
_engine.Execute(@"
'use strict';
function tst(a) {
let [let1, let2, let3] = a;
const [const1, const2, const3] = a;
var [var1, var2, var3] = a;

equal(1, var1);
equal(2, var2);
equal(undefined, var3);
equal(1, const1);
equal(2, const2);
equal(undefined, const3);
equal(1, let1);
equal(2, let2);
equal(undefined, let3);
}

tst([1,2])
");
}
}
}
34 changes: 34 additions & 0 deletions Jint.Tests/Runtime/FunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,39 @@ public void CanInvokeConstructorsFromEngine()
Assert.Equal("abc", arrayInstance[0]);
Assert.Equal(123, arrayInstance[1]);
}


[Fact]
public void FunctionInstancesCanBePassedToHost()
{
var engine = new Engine();
Func<JsValue, JsValue[], JsValue> ev = null;

void addListener(Func<JsValue, JsValue[], JsValue> callback)
{
ev = callback;
}

engine.SetValue("addListener", new Action<Func<JsValue, JsValue[], JsValue>>(addListener));

engine.Execute(@"
var a = 5;

(function() {
var acc = 10;
addListener(function (val) {
a = (val || 0) + acc;
});
})();
");

Assert.Equal(5, engine.Evaluate("a"));

ev(null, new JsValue[0]);
Assert.Equal(10, engine.Evaluate("a"));

ev(null, new JsValue[] { 20 });
Assert.Equal(30, engine.Evaluate("a"));
}
}
}
65 changes: 65 additions & 0 deletions Jint.Tests/Runtime/InteropTests.MemberAccess.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Jint.Native;
using Jint.Runtime.Interop;
using Jint.Tests.Runtime.Domain;
Expand Down Expand Up @@ -122,12 +125,74 @@ public void TypeReferenceShouldUseTypeResolverConfiguration()
Assert.True(engine.Evaluate("EchoService.Hidden").IsUndefined());
}

[Fact]
public void CustomDictionaryPropertyAccessTests()
{
var engine = new Engine(options =>
{
options.AllowClr();
});

var dc = new CustomDictionary<float>();
dc["a"] = 1;

engine.SetValue("dc", dc);

Assert.Equal(1, engine.Evaluate("dc.a"));
Assert.Equal(1, engine.Evaluate("dc['a']"));
Assert.NotEqual(JsValue.Undefined, engine.Evaluate("dc.Add"));
Assert.NotEqual(0, engine.Evaluate("dc.Add"));
Assert.Equal(JsValue.Undefined, engine.Evaluate("dc.b"));

engine.Execute("dc.b = 5");
engine.Execute("dc.Add('c', 10)");
Assert.Equal(5, engine.Evaluate("dc.b"));
Assert.Equal(10, engine.Evaluate("dc.c"));
}

private static class EchoService
{
public static string Echo(string message) => message;

[Obsolete]
public static string Hidden(string message) => message;
}

private class CustomDictionary<TValue> : IDictionary<string, TValue>
{
Dictionary<string, TValue> _dictionary = new Dictionary<string, TValue>();

public TValue this[string key]
{
get
{
if (TryGetValue(key, out var val)) return val;

// Normally, dictionary Item accessor throws an error when key does not exist
// But this is a custom implementation
return default;
}
set
{
_dictionary[key] = value;
}
}

public ICollection<string> Keys => _dictionary.Keys;
public ICollection<TValue> Values => _dictionary.Values;
public int Count => _dictionary.Count;
public bool IsReadOnly => false;
public void Add(string key, TValue value) => _dictionary.Add(key, value);
public void Add(KeyValuePair<string, TValue> item) => _dictionary.Add(item.Key, item.Value);
public void Clear() => _dictionary.Clear();
public bool Contains(KeyValuePair<string, TValue> item) => _dictionary.ContainsKey(item.Key);
public bool ContainsKey(string key) => _dictionary.ContainsKey(key);
public void CopyTo(KeyValuePair<string, TValue>[] array, int arrayIndex) => throw new NotImplementedException();
public IEnumerator<KeyValuePair<string, TValue>> GetEnumerator() => _dictionary.GetEnumerator();
public bool Remove(string key) => _dictionary.Remove(key);
public bool Remove(KeyValuePair<string, TValue> item) => _dictionary.Remove(item.Key);
public bool TryGetValue(string key, [MaybeNullWhen(false)] out TValue value) => _dictionary.TryGetValue(key, out value);
IEnumerator IEnumerable.GetEnumerator() => _dictionary.GetEnumerator();
}
}
}
1 change: 1 addition & 0 deletions Jint.Tests/Runtime/MethodAmbiguityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public void ShouldFavorOtherOverloadsOverObjectParameter()
Assert.Equal("Class2.Double[]", engine.Evaluate("Class2.Print([ 1, 2 ]); "));
Assert.Equal("Class2.ExpandoObject", engine.Evaluate("Class2.Print({ x: 1, y: 2 });"));
Assert.Equal("Class2.Int32", engine.Evaluate("Class2.Print(5);"));
Assert.Equal("Class2.Object", engine.Evaluate("Class2.Print(() => '');"));
}

private struct Class1
Expand Down
4 changes: 2 additions & 2 deletions Jint.Tests/Runtime/StringTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public void StringConcatenationAndReferences()
public void TrimLeftRightShouldBeSameAsTrimStartEnd()
{
_engine.Execute(@"
equal(''.trimLeft, ''.trimStart);
equal(''.trimRight, ''.trimEnd);
assert(''.trimLeft === ''.trimStart);
assert(''.trimRight === ''.trimEnd);
");
}
}
Expand Down
2 changes: 1 addition & 1 deletion Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ internal void RunBeforeExecuteStatementChecks(Statement statement)
constraint.Check();
}

if (_isDebugMode)
if (_isDebugMode && statement != null)
{
DebugHandler.OnStep(statement);
}
Expand Down
7 changes: 4 additions & 3 deletions Jint/Native/Object/ObjectInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ public bool TryGetValue(JsValue property, out JsValue value)
return true;
}

var getter = desc.Get ?? Undefined;
var getter = desc.Get ?? Undefined;
if (getter.IsUndefined())
{
value = Undefined;
Expand Down Expand Up @@ -961,7 +961,8 @@ private object ToObject(ObjectTraverseStack stack)
case ObjectClass.Function:
if (this is FunctionInstance function)
{
converted = (Func<JsValue, JsValue[], JsValue>) function.Call;
converted = new Func<JsValue, JsValue[], JsValue>(
(thisVal, args) => function.Engine.Invoke(function, (object) thisVal, args));
}

break;
Expand Down Expand Up @@ -1311,7 +1312,7 @@ internal ArrayInstance EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind
else
{
var objectInstance = _engine.Realm.Intrinsics.Array.ArrayCreate(2);
objectInstance.SetIndexValue(0, property, updateLength: false);
objectInstance.SetIndexValue(0, property, updateLength: false);
objectInstance.SetIndexValue(1, value, updateLength: false);
array.SetIndexValue(index, objectInstance, updateLength: false);
}
Expand Down
5 changes: 3 additions & 2 deletions Jint/Runtime/Descriptors/Specialized/ReflectionDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ internal sealed class ReflectionDescriptor : PropertyDescriptor
public ReflectionDescriptor(
Engine engine,
ReflectionAccessor reflectionAccessor,
object target)
: base(PropertyFlag.Enumerable | PropertyFlag.CustomJsValue)
object target,
bool enumerable)
: base((enumerable ? PropertyFlag.Enumerable : PropertyFlag.None) | PropertyFlag.CustomJsValue)
{
_engine = engine;
_reflectionAccessor = reflectionAccessor;
Expand Down
7 changes: 7 additions & 0 deletions Jint/Runtime/Interop/Reflection/IndexerAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Globalization;
using System.Reflection;
using Jint.Native;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Descriptors.Specialized;

namespace Jint.Runtime.Interop.Reflection
{
Expand Down Expand Up @@ -151,5 +153,10 @@ protected override void DoSetValue(object target, object value)
object[] parameters = {_key, value};
_setter!.Invoke(target, parameters);
}

public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target)
{
return new ReflectionDescriptor(engine, this, target, false);
}
}
}
2 changes: 1 addition & 1 deletion Jint/Runtime/Interop/Reflection/ReflectionAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ protected virtual object ConvertValueToSet(Engine engine, object value)

public virtual PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target)
{
return new ReflectionDescriptor(engine, this, target);
return new ReflectionDescriptor(engine, this, target, true);
}
}
}
20 changes: 12 additions & 8 deletions Jint/Runtime/Interop/TypeDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ internal sealed class TypeDescriptor
private static readonly Type _genericDictionaryType = typeof(IDictionary<,>);
private static readonly Type _stringType = typeof(string);

private readonly PropertyInfo _stringIndexer;
private readonly MethodInfo _tryGetValueMethod;
private readonly PropertyInfo _keysAccessor;
private readonly Type _valueType;

private TypeDescriptor(Type type)
{
Expand All @@ -24,13 +25,14 @@ private TypeDescriptor(Type type)
&& i.GetGenericTypeDefinition() == _genericDictionaryType
&& i.GenericTypeArguments[0] == _stringType)
{
_stringIndexer = i.GetProperty("Item");
_tryGetValueMethod = i.GetMethod("TryGetValue");
_keysAccessor = i.GetProperty("Keys");
_valueType = i.GenericTypeArguments[1];
break;
}
}

IsDictionary = _stringIndexer is not null || typeof(IDictionary).IsAssignableFrom(type);
IsDictionary = _tryGetValueMethod is not null || typeof(IDictionary).IsAssignableFrom(type);

// dictionaries are considered normal-object-like
IsArrayLike = !IsDictionary && DetermineIfObjectIsArrayLikeClrCollection(type);
Expand All @@ -47,7 +49,7 @@ private TypeDescriptor(Type type)
public bool IsArrayLike { get; }
public bool IsIntegerIndexedArray { get; }
public bool IsDictionary { get; }
public bool IsStringKeyedGenericDictionary => _stringIndexer is not null;
public bool IsStringKeyedGenericDictionary => _tryGetValueMethod is not null;
public bool IsEnumerable { get; }
public PropertyInfo LengthProperty { get; }

Expand Down Expand Up @@ -92,8 +94,10 @@ public bool TryGetValue(object target, string member, out object o)
// we could throw when indexing with an invalid key
try
{
o = _stringIndexer.GetValue(target, new [] { member });
return true;
var parameters = new[] { member, _valueType.IsValueType ? Activator.CreateInstance(_valueType) : null };
var result = (bool) _tryGetValueMethod.Invoke(target, parameters);
o = parameters[1];
return result;
}
catch (TargetInvocationException tie) when (tie.InnerException is KeyNotFoundException)
{
Expand All @@ -109,7 +113,7 @@ public ICollection<string> GetKeys(object target)
ExceptionHelper.ThrowInvalidOperationException("Not a string-keyed dictionary");
}

return (ICollection<string>)_keysAccessor.GetValue(target);
return (ICollection<string>) _keysAccessor.GetValue(target);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected override ExpressionResult EvaluateInternal(EvaluationContext context)
{
if (pattern is ArrayPattern ap)
{
return HandleArrayPattern(context, ap, argument, environment);
return HandleArrayPattern(context, ap, argument, environment, checkObjectPatternPropertyReference);
}

if (pattern is ObjectPattern op)
Expand Down Expand Up @@ -83,7 +83,8 @@ private static bool ConsumeFromIterator(IteratorInstance it, out JsValue value,
EvaluationContext context,
ArrayPattern pattern,
JsValue argument,
EnvironmentRecord environment)
EnvironmentRecord environment,
bool checkReference)
{
var engine = context.Engine;
var realm = engine.Realm;
Expand Down Expand Up @@ -141,7 +142,7 @@ private static bool ConsumeFromIterator(IteratorInstance it, out JsValue value,
ConsumeFromIterator(iterator, out value, out done);
}

AssignToIdentifier(engine, identifier.Name, value, environment);
AssignToIdentifier(engine, identifier.Name, value, environment, checkReference);
}
else if (left is MemberExpression me)
{
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ The entire execution engine was rebuild with performance in mind, in many cases
- ✔ Promises (Experimental, API is unstable)
- ✔ Reflect
- ✔ Proxies
- ✔ Reflect
- ✔ Symbols
- ❌ Tail calls
- ✔ Typed arrays
Expand Down