using System.Collections.Generic; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; namespace Jint.Native.Function { public abstract class FunctionInstance : ObjectInstance, ICallable { private const string PropertyNamePrototype = "prototype"; private const int PropertyNamePrototypeLength = 9; private PropertyDescriptor _prototype; private const string PropertyNameLength = "length"; private const int PropertyNameLengthLength = 6; private PropertyDescriptor _length; protected FunctionInstance(Engine engine, string[] parameters, LexicalEnvironment scope, bool strict) : this(engine, parameters, scope, strict, objectClass: "Function") { } protected FunctionInstance( Engine engine, string[] parameters, LexicalEnvironment scope, bool strict, in string objectClass) : base(engine, objectClass) { FormalParameters = parameters; Scope = scope; Strict = strict; } /// /// Executed when a function object is used as a function /// /// /// /// public abstract JsValue Call(JsValue thisObject, JsValue[] arguments); public LexicalEnvironment Scope { get; } public string[] FormalParameters { get; } public bool Strict { get; } public virtual bool HasInstance(JsValue v) { var vObj = v.TryCast(); if (ReferenceEquals(vObj, null)) { return false; } var po = Get("prototype"); if (!po.IsObject()) { throw new JavaScriptException(_engine.TypeError, $"Function has non-object prototype '{TypeConverter.ToString(po)}' in instanceof check"); } var o = po.AsObject(); if (ReferenceEquals(o, null)) { throw new JavaScriptException(_engine.TypeError); } while (true) { vObj = vObj.Prototype; if (ReferenceEquals(vObj, null)) { return false; } if (vObj == o) { return true; } } } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5.4 /// /// /// public override JsValue Get(string propertyName) { var v = base.Get(propertyName); if (propertyName.Length == 6 && propertyName == "caller" && ((v.As()?.Strict).GetValueOrDefault())) { throw new JavaScriptException(_engine.TypeError); } return v; } public override IEnumerable> GetOwnProperties() { if (_prototype != null) { yield return new KeyValuePair(PropertyNamePrototype, _prototype); } if (_length != null) { yield return new KeyValuePair(PropertyNameLength, _length); } foreach (var entry in base.GetOwnProperties()) { yield return entry; } } public override PropertyDescriptor GetOwnProperty(string propertyName) { if (propertyName.Length == PropertyNamePrototypeLength && propertyName == PropertyNamePrototype) { return _prototype ?? PropertyDescriptor.Undefined; } if (propertyName.Length == PropertyNameLengthLength && propertyName == PropertyNameLength) { return _length ?? PropertyDescriptor.Undefined; } return base.GetOwnProperty(propertyName); } protected internal override void SetOwnProperty(string propertyName, PropertyDescriptor desc) { if (propertyName.Length == PropertyNamePrototypeLength && propertyName == PropertyNamePrototype) { _prototype = desc; } else if (propertyName.Length == PropertyNameLengthLength && propertyName == PropertyNameLength) { _length = desc; } else { base.SetOwnProperty(propertyName, desc); } } public override bool HasOwnProperty(string propertyName) { if (propertyName.Length == PropertyNamePrototypeLength && propertyName == PropertyNamePrototype) { return _prototype != null; } if (propertyName.Length == PropertyNameLengthLength && propertyName == PropertyNameLength) { return _length != null; } return base.HasOwnProperty(propertyName); } public override void RemoveOwnProperty(string propertyName) { if (propertyName.Length == PropertyNamePrototypeLength && propertyName == PropertyNamePrototype) { _prototype = null; } if (propertyName.Length == PropertyNameLengthLength && propertyName == PropertyNameLength) { _prototype = null; } base.RemoveOwnProperty(propertyName); } } }