/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Microsoft Public License. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Microsoft Public License, please send an email to * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Microsoft Public License. * * You must not remove this notice, or any other, from this software. * * * ***************************************************************************/ using System; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Dynamic; using Microsoft.Scripting.Generation; using Microsoft.Scripting.Runtime; using Microsoft.Scripting.Utils; using AstUtils = Microsoft.Scripting.Ast.Utils; namespace Microsoft.Scripting.Actions { using Ast = System.Linq.Expressions.Expression; public partial class DefaultBinder : ActionBinder { /// /// Builds a MetaObject for performing a member get. Supports all built-in .NET members, the OperatorMethod /// GetBoundMember, and StrongBox instances. /// /// /// The name of the member to retrieve. This name is not processed by the DefaultBinder and /// is instead handed off to the GetMember API which can do name mangling, case insensitive lookups, etc... /// /// /// The MetaObject from which the member is retrieved. /// /// /// The value being assigned to the target member. /// public MetaObject SetMember(string name, MetaObject target, MetaObject value) { return SetMember(name, target, value, Ast.Constant(null, typeof(CodeContext))); } /// /// Builds a MetaObject for performing a member get. Supports all built-in .NET members, the OperatorMethod /// GetBoundMember, and StrongBox instances. /// /// /// The name of the member to retrieve. This name is not processed by the DefaultBinder and /// is instead handed off to the GetMember API which can do name mangling, case insensitive lookups, etc... /// /// /// The MetaObject from which the member is retrieved. /// /// /// The value being assigned to the target member. /// /// /// An expression which provides access to the CodeContext if its required for /// accessing the member (e.g. for an extension property which takes CodeContext). By default this /// a null CodeContext object is passed. /// public MetaObject SetMember(string name, MetaObject target, MetaObject value, Expression codeContext) { ContractUtils.RequiresNotNull(name, "name"); ContractUtils.RequiresNotNull(target, "target"); ContractUtils.RequiresNotNull(value, "value"); ContractUtils.RequiresNotNull(codeContext, "codeContext"); return MakeSetMemberTarget( new SetOrDeleteMemberInfo(name, codeContext), target, value ); } private MetaObject MakeSetMemberTarget(SetOrDeleteMemberInfo memInfo, MetaObject target, MetaObject value) { Type type = target.LimitType.IsCOMObject ? target.Expression.Type : target.LimitType; Expression self = target.Expression; target = target.Restrict(target.LimitType); memInfo.Body.Restrictions = target.Restrictions; if (typeof(TypeTracker).IsAssignableFrom(type)) { type = ((TypeTracker)target.Value).Type; self = null; memInfo.Body.Restrictions = memInfo.Body.Restrictions.Merge( Restrictions.GetInstanceRestriction(target.Expression, target.Value) ); } MakeSetMemberRule(memInfo, type, self, value); return memInfo.Body.GetMetaObject(target, value); } private void MakeSetMemberRule(SetOrDeleteMemberInfo memInfo, Type type, Expression self, MetaObject target) { if (MakeOperatorSetMemberBody(memInfo, self, target, type, "SetMember")) { return; } // needed for GetMember call until DynamicAction goes away OldDynamicAction act = OldSetMemberAction.Make( this, memInfo.Name ); MemberGroup members = GetMember(act, type, memInfo.Name); // if lookup failed try the strong-box type if available. if (members.Count == 0 && typeof(IStrongBox).IsAssignableFrom(type)) { self = Ast.Field(AstUtils.Convert(self, type), type.GetField("Value")); type = type.GetGenericArguments()[0]; members = GetMember(act, type, memInfo.Name); } Expression error; TrackerTypes memberTypes = GetMemberType(members, out error); if (error == null) { switch (memberTypes) { case TrackerTypes.Method: case TrackerTypes.TypeGroup: case TrackerTypes.Type: case TrackerTypes.Constructor: memInfo.Body.FinishCondition( MakeError(MakeReadOnlyMemberError(type, memInfo.Name)) ); break; case TrackerTypes.Event: memInfo.Body.FinishCondition( MakeError(MakeEventValidation(members, self, target.Expression, memInfo.CodeContext)) ); break; case TrackerTypes.Field: MakeFieldRule(memInfo, self, target, type, members); break; case TrackerTypes.Property: MakePropertyRule(memInfo, self, target, type, members); break; case TrackerTypes.Custom: MakeGenericBody(memInfo, self, target, type, members[0]); break; case TrackerTypes.All: // no match if (MakeOperatorSetMemberBody(memInfo, self, target, type, "SetMemberAfter")) { return; } memInfo.Body.FinishCondition( MakeError(MakeMissingMemberError(type, memInfo.Name)) ); break; default: throw new InvalidOperationException(); } } else { memInfo.Body.FinishCondition(error); } } private void MakeGenericBody(SetOrDeleteMemberInfo memInfo, Expression instance, MetaObject target, Type type, MemberTracker tracker) { if (instance != null) { tracker = tracker.BindToInstance(instance); } Expression val = tracker.SetValue(memInfo.CodeContext, this, type, target.Expression); if (val != null) { memInfo.Body.FinishCondition(val); } else { memInfo.Body.FinishCondition( MakeError(tracker.GetError(this)) ); } } private void MakePropertyRule(SetOrDeleteMemberInfo memInfo, Expression instance, MetaObject target, Type targetType, MemberGroup properties) { PropertyTracker info = (PropertyTracker)properties[0]; MethodInfo setter = info.GetSetMethod(true); // Allow access to protected getters TODO: this should go, it supports IronPython semantics. if (setter != null && !setter.IsPublic && !(setter.IsFamily || setter.IsFamilyOrAssembly)) { if (!PrivateBinding) { setter = null; } } if (setter != null) { setter = CompilerHelpers.GetCallableMethod(setter, PrivateBinding); if (info.IsStatic != (instance == null)) { memInfo.Body.FinishCondition( MakeError( MakeStaticPropertyInstanceAccessError( info, true, instance, target.Expression ) ) ); } else if (info.IsStatic && info.DeclaringType != targetType) { memInfo.Body.FinishCondition( MakeError( MakeStaticAssignFromDerivedTypeError(targetType, info, target.Expression, memInfo.CodeContext) ) ); } else if (setter.ContainsGenericParameters) { memInfo.Body.FinishCondition( MakeGenericPropertyExpression(memInfo) ); } else if (setter.IsPublic && !setter.DeclaringType.IsValueType) { if (instance == null) { memInfo.Body.FinishCondition( AstUtils.SimpleCallHelper( setter, ConvertExpression( target.Expression, setter.GetParameters()[0].ParameterType, ConversionResultKind.ExplicitCast, memInfo.CodeContext ) ) ); } else { memInfo.Body.FinishCondition( MakeReturnValue( MakeCallExpression(memInfo.CodeContext, setter, instance, target.Expression), target ) ); } } else { // TODO: Should be able to do better w/ value types. memInfo.Body.FinishCondition( MakeReturnValue( Ast.Call( Ast.Constant(((ReflectedPropertyTracker)info).Property), // TODO: Private binding on extension properties typeof(PropertyInfo).GetMethod("SetValue", new Type[] { typeof(object), typeof(object), typeof(object[]) }), instance == null ? Ast.Constant(null) : AstUtils.Convert(instance, typeof(object)), AstUtils.Convert(target.Expression, typeof(object)), Ast.NewArrayInit(typeof(object)) ), target ) ); } } else { memInfo.Body.FinishCondition( MakeError( MakeMissingMemberError(targetType, memInfo.Name) ) ); } } private void MakeFieldRule(SetOrDeleteMemberInfo memInfo, Expression instance, MetaObject target, Type targetType, MemberGroup fields) { FieldTracker field = (FieldTracker)fields[0]; // TODO: Tmp variable for target if (field.DeclaringType.IsGenericType && field.DeclaringType.GetGenericTypeDefinition() == typeof(StrongBox<>)) { // work around a CLR bug where we can't access generic fields from dynamic methods. Type[] generic = field.DeclaringType.GetGenericArguments(); memInfo.Body.FinishCondition( MakeReturnValue( Ast.Assign( Ast.Field( AstUtils.Convert(instance, field.DeclaringType), field.DeclaringType.GetField("Value") ), AstUtils.Convert(target.Expression, generic[0]) ), target ) ); } else if (field.IsInitOnly || field.IsLiteral) { memInfo.Body.FinishCondition( MakeError( MakeReadOnlyMemberError(targetType, memInfo.Name) ) ); } else if (field.IsStatic && targetType != field.DeclaringType) { memInfo.Body.FinishCondition( MakeError( MakeStaticAssignFromDerivedTypeError(targetType, field, target.Expression, memInfo.CodeContext) ) ); } else if (field.DeclaringType.IsValueType && !field.IsStatic) { memInfo.Body.FinishCondition( Ast.Throw( Ast.New( typeof(ArgumentException).GetConstructor(new Type[] { typeof(string) }), Ast.Constant("cannot assign to value types") ) ) ); } else if (field.IsPublic && field.DeclaringType.IsVisible) { memInfo.Body.FinishCondition( MakeReturnValue( Ast.Assign( Ast.Field( field.IsStatic ? null : Ast.Convert(instance, field.DeclaringType), field.Field ), ConvertExpression(target.Expression, field.FieldType, ConversionResultKind.ExplicitCast, memInfo.CodeContext) ), target ) ); } else { memInfo.Body.FinishCondition( MakeReturnValue( Ast.Call( AstUtils.Convert(Ast.Constant(field.Field), typeof(FieldInfo)), typeof(FieldInfo).GetMethod("SetValue", new Type[] { typeof(object), typeof(object) }), field.IsStatic ? Ast.Constant(null) : (Expression)AstUtils.Convert(instance, typeof(object)), AstUtils.Convert(target.Expression, typeof(object)) ), target ) ); } } private Expression MakeReturnValue(Expression expression, MetaObject target) { return Ast.Block( expression, target.Expression ); } /// if a member-injector is defined-on or registered-for this type call it private bool MakeOperatorSetMemberBody(SetOrDeleteMemberInfo memInfo, Expression self, MetaObject target, Type type, string name) { if (self != null) { MethodInfo setMem = GetMethod(type, name); if (setMem != null && setMem.IsSpecialName) { ParameterExpression tmp = Ast.Variable(target.Expression.Type, "setValue"); memInfo.Body.AddVariable(tmp); Expression call = MakeCallExpression(memInfo.CodeContext, setMem, AstUtils.Convert(self, type), Ast.Constant(memInfo.Name), tmp); call = Ast.Block(Ast.Assign(tmp, target.Expression), call); if (setMem.ReturnType == typeof(bool)) { memInfo.Body.AddCondition( call, tmp ); } else { memInfo.Body.FinishCondition(Ast.Block(call, tmp)); } return setMem.ReturnType != typeof(bool); } } return false; } private static Expression MakeGenericPropertyExpression(SetOrDeleteMemberInfo memInfo) { return Ast.New( typeof(MemberAccessException).GetConstructor(new Type[] { typeof(string) }), Ast.Constant(memInfo.Name) ); } } }