// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics; using System.Dynamic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.InteropServices.Expando; using Microsoft.ClearScript.Util; namespace Microsoft.ClearScript { internal abstract class ScriptItem : ScriptObject, IExpando, IDynamic, IScriptMarshalWrapper { private static readonly MethodInfo throwLastScriptErrorMethod = typeof(ScriptItem).GetMethod("ThrowLastScriptError"); private static readonly MethodInfo clearLastScriptErrorMethod = typeof(ScriptItem).GetMethod("ClearLastScriptError"); [ThreadStatic] private static IScriptEngineException lastScriptError; public static void ThrowLastScriptError() { var scriptError = lastScriptError; if (scriptError != null) { if (scriptError is ScriptInterruptedException) { throw new ScriptInterruptedException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptException, scriptError.InnerException); } throw new ScriptEngineException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptException, scriptError.InnerException); } } public static void ClearLastScriptError() { lastScriptError = null; } protected abstract bool TryBindAndInvoke(DynamicMetaObjectBinder binder, object[] args, out object result); protected virtual object[] AdjustInvokeArgs(object[] args) { return args; } private bool TryWrappedBindAndInvoke(DynamicMetaObjectBinder binder, object[] wrappedArgs, out object result) { object[] args = null; object[] savedArgs = null; object tempResult = null; var succeeded = Engine.ScriptInvoke(() => { args = Engine.MarshalToScript(wrappedArgs); savedArgs = (object[])args.Clone(); if (!TryBindAndInvoke(binder, args, out tempResult)) { if ((Engine.CurrentScriptFrame != null) && (lastScriptError == null)) { lastScriptError = Engine.CurrentScriptFrame.ScriptError; } return false; } return true; }); if (succeeded) { for (var index = 0; index < args.Length; index++) { var arg = args[index]; if (!ReferenceEquals(arg, savedArgs[index])) { wrappedArgs[index] = Engine.MarshalToHost(args[index], false); } } result = Engine.MarshalToHost(tempResult, false).ToDynamicResult(Engine); return true; } result = null; return false; } private bool TryWrappedInvokeOrInvokeMember(DynamicMetaObjectBinder binder, ParameterInfo[] parameters, object[] args, out object result) { Type[] paramTypes = null; if ((parameters != null) && (parameters.Length >= args.Length)) { paramTypes = parameters.Skip(parameters.Length - args.Length).Select(param => param.ParameterType).ToArray(); } if (paramTypes != null) { for (var index = 0; index < paramTypes.Length; index++) { var paramType = paramTypes[index]; if (paramType.IsByRef) { args[index] = typeof(HostVariable<>).MakeSpecificType(paramType.GetElementType()).CreateInstance(args[index]); } } } if (TryWrappedBindAndInvoke(binder, AdjustInvokeArgs(args), out result)) { if (paramTypes != null) { for (var index = 0; index < paramTypes.Length; index++) { if (paramTypes[index].IsByRef) { if (args[index] is IHostVariable hostVariable) { args[index] = hostVariable.Value; } } } } return true; } return false; } private string[] GetAllPropertyNames() { return GetPropertyNames().Concat(GetPropertyIndices().Select(index => index.ToString(CultureInfo.InvariantCulture))).ToArray(); } private static DynamicMetaObject PostProcessBindResult(DynamicMetaObject result) { var catchBody = Expression.Block(Expression.Call(throwLastScriptErrorMethod), Expression.Rethrow(), Expression.Default(result.Expression.Type)); return new DynamicMetaObject(Expression.TryCatchFinally(result.Expression, Expression.Call(clearLastScriptErrorMethod), Expression.Catch(typeof(Exception), catchBody)), result.Restrictions); } #region ScriptObject overrides public override IEnumerable PropertyNames => GetPropertyNames(); public override IEnumerable PropertyIndices => GetPropertyIndices(); public new object this[string name, params object[] args] { get => GetProperty(name, args).ToDynamicResult(Engine); set => SetProperty(name, args.Concat(value.ToEnumerable()).ToArray()); } public new object this[int index] { get => GetProperty(index).ToDynamicResult(Engine); set => SetProperty(index, value); } #endregion #region DynamicObject overrides public override DynamicMetaObject GetMetaObject(Expression param) { return new MetaScriptItem(base.GetMetaObject(param)); } public override IEnumerable GetDynamicMemberNames() { return GetPropertyNames().Concat(GetPropertyIndices().Select(index => index.ToString(CultureInfo.InvariantCulture))); } public override bool TryGetMember(GetMemberBinder binder, out object result) { return TryWrappedBindAndInvoke(binder, ArrayHelpers.GetEmptyArray(), out result); } public override bool TrySetMember(SetMemberBinder binder, object value) { return TryWrappedBindAndInvoke(binder, new[] { value }, out _); } public override bool TryGetIndex(GetIndexBinder binder, object[] indices, out object result) { return TryWrappedBindAndInvoke(binder, indices, out result); } public override bool TrySetIndex(SetIndexBinder binder, object[] indices, object value) { return TryWrappedBindAndInvoke(binder, indices.Concat(value.ToEnumerable()).ToArray(), out _); } public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) { ParameterInfo[] parameters = null; if (Engine.EnableAutoHostVariables) { parameters = new StackFrame(1, false).GetMethod().GetParameters(); } return TryWrappedInvokeOrInvokeMember(binder, parameters, args, out result); } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { ParameterInfo[] parameters = null; if (Engine.EnableAutoHostVariables) { parameters = new StackFrame(1, false).GetMethod().GetParameters(); } return TryWrappedInvokeOrInvokeMember(binder, parameters, args, out result); } #endregion #region IReflect implementation public MethodInfo GetMethod(string name, BindingFlags bindFlags, Binder binder, Type[] types, ParameterModifier[] modifiers) { throw new NotImplementedException(); } public MethodInfo GetMethod(string name, BindingFlags bindFlags) { throw new NotImplementedException(); } public MethodInfo[] GetMethods(BindingFlags bindFlags) { return ArrayHelpers.GetEmptyArray(); } public FieldInfo GetField(string name, BindingFlags bindFlags) { throw new NotImplementedException(); } public FieldInfo[] GetFields(BindingFlags bindFlags) { return MemberMap.GetFields(GetAllPropertyNames()); } public PropertyInfo GetProperty(string name, BindingFlags bindFlags) { throw new NotImplementedException(); } public PropertyInfo GetProperty(string name, BindingFlags bindFlags, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) { throw new NotImplementedException(); } public PropertyInfo[] GetProperties(BindingFlags bindFlags) { return ArrayHelpers.GetEmptyArray(); } public MemberInfo[] GetMember(string name, BindingFlags bindFlags) { // ReSharper disable once CoVariantArrayConversion return new [] { MemberMap.GetField(name) }; } public MemberInfo[] GetMembers(BindingFlags bindFlags) { // ReSharper disable once CoVariantArrayConversion return GetFields(bindFlags); } public object InvokeMember(string name, BindingFlags invokeFlags, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) { if (invokeFlags.HasFlag(BindingFlags.InvokeMethod)) { if (name == SpecialMemberNames.Default) { return Invoke(false, args); } return InvokeMethod(name, args); } if (invokeFlags.HasFlag(BindingFlags.GetField)) { if (int.TryParse(name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var index)) { return GetProperty(index); } return GetProperty(name, args); } if (invokeFlags.HasFlag(BindingFlags.SetField)) { if (args.Length != 1) { throw new InvalidOperationException("Invalid argument count"); } var value = args[0]; if (int.TryParse(name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var index)) { SetProperty(index, value); return value; } SetProperty(name, args); return value; } throw new InvalidOperationException("Invalid member access mode"); } public Type UnderlyingSystemType => throw new NotImplementedException(); #endregion #region IExpando implementation public FieldInfo AddField(string name) { throw new NotImplementedException(); } public PropertyInfo AddProperty(string name) { throw new NotImplementedException(); } public MethodInfo AddMethod(string name, Delegate method) { throw new NotImplementedException(); } public void RemoveMember(MemberInfo member) { throw new NotImplementedException(); } #endregion #region IDynamic implementation public object GetProperty(string name, out bool isCacheable, params object[] args) { isCacheable = false; return GetProperty(name, args); } #endregion #region IDynamic implementation (abstract) public abstract string[] GetPropertyNames(); public abstract int[] GetPropertyIndices(); #endregion #region IScriptMarshalWrapper implementation (abstract) public abstract object Unwrap(); #endregion #region Nested type: MetaScriptItem private sealed class MetaScriptItem : DynamicMetaObject { private readonly DynamicMetaObject metaDynamic; public MetaScriptItem(DynamicMetaObject metaDynamic) : base(metaDynamic.Expression, metaDynamic.Restrictions, metaDynamic.Value) { this.metaDynamic = metaDynamic; } #region DynamicMetaObject overrides public override IEnumerable GetDynamicMemberNames() { return metaDynamic.GetDynamicMemberNames(); } public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) { return PostProcessBindResult(metaDynamic.BindBinaryOperation(binder, arg)); } public override DynamicMetaObject BindConvert(ConvertBinder binder) { return PostProcessBindResult(metaDynamic.BindConvert(binder)); } public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) { return PostProcessBindResult(metaDynamic.BindCreateInstance(binder, args)); } public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) { return PostProcessBindResult(metaDynamic.BindDeleteIndex(binder, indexes)); } public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) { return PostProcessBindResult(metaDynamic.BindDeleteMember(binder)); } public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { return PostProcessBindResult(metaDynamic.BindGetIndex(binder, indexes)); } public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { return PostProcessBindResult(metaDynamic.BindGetMember(binder)); } public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) { return PostProcessBindResult(metaDynamic.BindInvoke(binder, args)); } public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { return PostProcessBindResult(metaDynamic.BindInvokeMember(binder, args)); } public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { return PostProcessBindResult(metaDynamic.BindSetIndex(binder, indexes, value)); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { return PostProcessBindResult(metaDynamic.BindSetMember(binder, value)); } public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) { return PostProcessBindResult(metaDynamic.BindUnaryOperation(binder)); } #endregion } #endregion } }