using System; using System.Collections.Generic; using Esprima.Ast; using Jint.Collections; using Jint.Native; using Jint.Native.Argument; using Jint.Native.Function; namespace Jint.Runtime.Environments { /// /// Represents a declarative environment record /// http://www.ecma-international.org/ecma-262/5.1/#sec-10.2.1.1 /// public sealed class DeclarativeEnvironmentRecord : EnvironmentRecord { private StringDictionarySlim _dictionary; private bool _set; private string _key; private Binding _value; private const string BindingNameArguments = "arguments"; private Binding _argumentsBinding; public DeclarativeEnvironmentRecord(Engine engine) : base(engine) { } private void SetItem(string key, in Binding value) { if (_set && _key != key) { if (_dictionary == null) { _dictionary = new StringDictionarySlim(); } _dictionary[_key] = _value; } _set = true; _key = key; _value = value; if (_dictionary != null) { _dictionary[key] = value; } } private ref Binding GetExistingItem(string key) { if (_set && _key == key) { return ref _value; } if (key.Length == 9 && key == BindingNameArguments) { return ref _argumentsBinding; } return ref _dictionary[key]; } private bool ContainsKey(string key) { if (key.Length == 9 && key == BindingNameArguments) { return !ReferenceEquals(_argumentsBinding.Value, null); } if (_set && key == _key) { return true; } return _dictionary?.ContainsKey(key) == true; } private void Remove(string key) { if (_set && key == _key) { _set = false; _key = null; _value = default; } if (key == BindingNameArguments) { _argumentsBinding.Value = null; } else { _dictionary?.Remove(key); } } private bool TryGetValue(string key, out Binding value) { value = default; if (_set && _key == key) { value = _value; return true; } return _dictionary != null && _dictionary.TryGetValue(key, out value); } public override bool HasBinding(string name) { return ContainsKey(name); } public override void CreateMutableBinding(string name, JsValue value, bool canBeDeleted = false) { SetItem(name, new Binding(value, canBeDeleted, mutable: true)); } public override void SetMutableBinding(string name, JsValue value, bool strict) { ref var binding = ref GetExistingItem(name); if (binding.Mutable) { binding.Value = value; } else { if (strict) { ExceptionHelper.ThrowTypeError(_engine, "Can't update the value of an immutable binding."); } } } public override JsValue GetBindingValue(string name, bool strict) { ref var binding = ref GetExistingItem(name); if (!binding.Mutable && binding.Value._type == Types.Undefined) { if (strict) { ExceptionHelper.ThrowReferenceError(_engine, "Can't access an uninitialized immutable binding."); } return Undefined; } return binding.Value; } public override bool DeleteBinding(string name) { ref Binding binding = ref GetExistingItem(name); if (ReferenceEquals(binding.Value, null)) { return true; } if (!binding.CanBeDeleted) { return false; } Remove(name); return true; } public override JsValue ImplicitThisValue() { return Undefined; } /// public override string[] GetAllBindingNames() { int size = _set ? 1 : 0; if (!ReferenceEquals(_argumentsBinding.Value, null)) { size += 1; } if (_dictionary != null) { size += _dictionary.Count; } var keys = size > 0 ? new string[size] : ArrayExt.Empty(); int n = 0; if (_set) { keys[n++] = _key; } if (!ReferenceEquals(_argumentsBinding.Value, null)) { keys[n++] = BindingNameArguments; } _dictionary?.Keys.CopyTo(keys, n); return keys; } internal void ReleaseArguments() { _engine._argumentsInstancePool.Return(_argumentsBinding.Value as ArgumentsInstance); _argumentsBinding = default; } /// /// Optimized version for function calls. /// internal void AddFunctionParameters( FunctionInstance functionInstance, JsValue[] arguments, ArgumentsInstance argumentsInstance) { var parameters = functionInstance._formalParameters; bool empty = _dictionary == null && !_set; if (empty && parameters.Length == 1 && parameters[0].Length != BindingNameArguments.Length) { var jsValue = arguments.Length == 0 ? Undefined : arguments[0]; var binding = new Binding(jsValue, false, true); _set = true; _key = parameters[0]; _value = binding; } else { AddMultipleParameters(arguments, parameters); } if (ReferenceEquals(_argumentsBinding.Value, null)) { _argumentsBinding = new Binding(argumentsInstance, canBeDeleted: false, mutable: true); } } private void AddMultipleParameters(JsValue[] arguments, string[] parameters) { bool empty = _dictionary == null && !_set; for (var i = 0; i < parameters.Length; i++) { var argName = parameters[i]; var jsValue = i + 1 > arguments.Length ? Undefined : arguments[i]; if (empty || !TryGetValue(argName, out var existing)) { var binding = new Binding(jsValue, false, true); if (argName.Length == 9 && argName == BindingNameArguments) { _argumentsBinding = binding; } else { SetItem(argName, binding); } } else { if (existing.Mutable) { ref var b = ref GetExistingItem(argName); b.Value = jsValue; } else { ExceptionHelper.ThrowTypeError(_engine, "Can't update the value of an immutable binding."); } } } } internal void AddVariableDeclarations(List variableDeclarations) { var variableDeclarationsCount = variableDeclarations.Count; for (var i = 0; i < variableDeclarationsCount; i++) { var variableDeclaration = variableDeclarations[i]; var declarationsCount = variableDeclaration.Declarations.Count; for (var j = 0; j < declarationsCount; j++) { var d = variableDeclaration.Declarations[j]; var dn = ((Identifier) d.Id).Name; if (!ContainsKey(dn)) { var binding = new Binding(Undefined, canBeDeleted: false, mutable: true); SetItem(dn, binding); } } } } } }