// ==++== // // // Copyright (c) 2002 Microsoft Corporation. All rights reserved. // // The use and distribution terms for this software are contained in the file // named license.txt, which can be found in the root of this distribution. // By using this software in any fashion, you are agreeing to be bound by the // terms of this license. // // You must not remove this notice, or any other, from this software. // // // ==--== namespace Microsoft.JScript { using System; using System.Collections; using System.Configuration.Assemblies; using System.Globalization; using System.Reflection; using System.Reflection.Emit; using System.Security.Permissions; internal sealed class CustomAttribute : AST{ private AST ctor; private ASTList args; private AST target; //indicates what kind of member the custom attribute is being applied to internal Object type; // will contain either the Type or ClassScope for the custom attribute type private ArrayList positionalArgValues; private ArrayList namedArgFields; private ArrayList namedArgFieldValues; private ArrayList namedArgProperties; private ArrayList namedArgPropertyValues; internal bool raiseToPropertyLevel; internal CustomAttribute(Context context, AST func, ASTList args) : base(context) { this.ctor = func; this.args = args; this.target = null; this.type = null; this.positionalArgValues = new ArrayList(); this.namedArgFields = new ArrayList(); this.namedArgFieldValues = new ArrayList(); this.namedArgProperties = new ArrayList(); this.namedArgPropertyValues = new ArrayList(); this.raiseToPropertyLevel = false; } //Check that custom attribute is applicable to its target private bool CheckIfTargetOK(Object caType){ if (caType == null) return false; AttributeTargets validOn = (AttributeTargets)0; Type caTypeT = caType as Type; if (caTypeT != null){ Object[] usage = caTypeT.GetCustomAttributes(typeof(AttributeUsageAttribute), true); validOn = ((AttributeUsageAttribute)usage[0]).ValidOn; }else validOn = ((ClassScope)caType).owner.validOn; Object target = this.target; Class c = target as Class; if (c != null){ if (c.isInterface){ if ((validOn & AttributeTargets.Interface) != 0) return true; }else if (c is EnumDeclaration){ if ((validOn & AttributeTargets.Enum) != 0) return true; }else if ((validOn & AttributeTargets.Class) != 0){ if (caTypeT == typeof(AttributeUsageAttribute)){ //This is an attribute that describes a new attribute class //Set the validOn field of Class so that it becomes easier to check the usage of the new attribute if (this.positionalArgValues.Count > 0){ Object par0 = this.positionalArgValues[0]; if (par0 is AttributeTargets) c.validOn = (AttributeTargets)par0; } for (int i = 0, n = this.namedArgProperties.Count; i < n; i++){ PropertyInfo prop = this.namedArgProperties[i] as PropertyInfo; if (prop.Name == "AllowMultiple") c.allowMultiple = (bool)this.namedArgPropertyValues[i]; } } return true; }else if (caTypeT.FullName == "System.NonSerializedAttribute"){ c.attributes &= ~TypeAttributes.Serializable; return false; } this.context.HandleError(JSError.InvalidCustomAttributeTarget, CustomAttribute.GetTypeName(caType)); return false; } FunctionDeclaration fDecl = target as FunctionDeclaration; if (fDecl != null){ if ((validOn & AttributeTargets.Property) != 0 && fDecl.enclosingProperty != null){ if (fDecl.enclosingProperty.getter == null || ((JSFieldMethod)fDecl.enclosingProperty.getter).func == fDecl.func){ this.raiseToPropertyLevel = true; return true; }else{ this.context.HandleError(JSError.PropertyLevelAttributesMustBeOnGetter); return false; } } if ((validOn & AttributeTargets.Method) != 0 && fDecl.isMethod) return true; if ((validOn & AttributeTargets.Constructor) != 0 && fDecl.func.isConstructor) return true; this.context.HandleError(JSError.InvalidCustomAttributeTarget, CustomAttribute.GetTypeName(caType)); return false; } if (target is VariableDeclaration || target is Constant){ if ((validOn & AttributeTargets.Field) != 0) return true; this.context.HandleError(JSError.InvalidCustomAttributeTarget, CustomAttribute.GetTypeName(caType)); return false; } if (target is AssemblyCustomAttributeList && (validOn & AttributeTargets.Assembly) != 0) return true; if (target == null && (validOn & AttributeTargets.Parameter) != 0) return true; this.context.HandleError(JSError.InvalidCustomAttributeTarget, CustomAttribute.GetTypeName(caType)); return false; } private static ushort DaysSince2000(){ return (ushort)(DateTime.Now - new DateTime(2000, 1, 1)).Days; } internal override Object Evaluate(){ ConstructorInfo c = (ConstructorInfo)((Binding)this.ctor).member; ParameterInfo[] pars = c.GetParameters(); int pn = pars.Length; for (int i = positionalArgValues.Count; i < pn; i++) positionalArgValues.Add(Convert.CoerceT(null, pars[i].ParameterType)); Object[] pArgVals = new Object[pn]; positionalArgValues.CopyTo(0, pArgVals, 0, pn); Object ca = c.Invoke(BindingFlags.ExactBinding, null, pArgVals, null); for (int i = 0, n = this.namedArgProperties.Count; i < n; i++){ JSProperty prop = this.namedArgProperties[i] as JSProperty; if (prop != null) prop.SetValue(ca, Convert.Coerce(this.namedArgPropertyValues[i], prop.PropertyIR()), null); else ((PropertyInfo)this.namedArgProperties[i]).SetValue(ca, this.namedArgPropertyValues[i], null); } for (int i = 0, n = this.namedArgFields.Count; i < n; i++){ JSVariableField field = this.namedArgFields[i] as JSVariableField; if (field != null) field.SetValue(ca, Convert.Coerce(this.namedArgFieldValues[i], field.GetInferredType(null))); else ((FieldInfo)this.namedArgFields[i]).SetValue(ca, this.namedArgFieldValues[i]); } return ca; } internal CLSComplianceSpec GetCLSComplianceValue(){ Debug.Assert(this.type == Typeob.CLSCompliantAttribute); return ((bool)this.positionalArgValues[0]) ? CLSComplianceSpec.CLSCompliant : CLSComplianceSpec.NonCLSCompliant; } private void ConvertClassScopesAndEnumWrappers(ArrayList vals){ for (int i = 0, n = vals.Count; i < n; i++){ ClassScope csc = vals[i] as ClassScope; if (csc != null){ vals[i] = csc.GetTypeBuilder(); continue;} EnumWrapper wrapper = vals[i] as EnumWrapper; if (wrapper != null) vals[i] = wrapper.ToNumericValue(); } } private void ConvertFieldAndPropertyInfos(ArrayList vals){ for (int i = 0, n = vals.Count; i < n; i++){ JSField jsfld = vals[i] as JSField; if (jsfld != null){ vals[i] = jsfld.GetMetaData(); continue;} JSProperty jsprop = vals[i] as JSProperty; if (jsprop != null){ vals[i] = jsprop.metaData; continue;} } } internal CustomAttributeBuilder GetCustomAttribute(){ ConstructorInfo c = (ConstructorInfo)((Binding)this.ctor).member; ParameterInfo[] pars = c.GetParameters(); int pn = pars.Length; if (c is JSConstructor) c = ((JSConstructor)c).GetConstructorInfo(compilerGlobals); this.ConvertClassScopesAndEnumWrappers(this.positionalArgValues); this.ConvertClassScopesAndEnumWrappers(this.namedArgPropertyValues); this.ConvertClassScopesAndEnumWrappers(this.namedArgFieldValues); this.ConvertFieldAndPropertyInfos(this.namedArgProperties); this.ConvertFieldAndPropertyInfos(this.namedArgFields); for (int i = positionalArgValues.Count; i < pn; i++) positionalArgValues.Add(Convert.CoerceT(null, pars[i].ParameterType)); Object[] pArgVals = new Object[pn]; positionalArgValues.CopyTo(0, pArgVals, 0, pn); PropertyInfo[] nArgProps = new PropertyInfo[namedArgProperties.Count]; namedArgProperties.CopyTo(nArgProps); Object[] nArgPVals = new Object[namedArgPropertyValues.Count]; namedArgPropertyValues.CopyTo(nArgPVals); FieldInfo[] nArgFields = new FieldInfo[namedArgFields.Count]; namedArgFields.CopyTo(nArgFields); Object[] nArgFVals = new Object[namedArgFieldValues.Count]; namedArgFieldValues.CopyTo(nArgFVals); return new CustomAttributeBuilder(c, pArgVals, nArgProps, nArgPVals, nArgFields, nArgFVals); } internal Object GetTypeIfAttributeHasToBeUnique(){ Type t = this.type as Type; if (t != null){ Object[] custAttrs = t.GetCustomAttributes(typeof(AttributeUsageAttribute), false); if (custAttrs.Length > 0 && !((AttributeUsageAttribute)custAttrs[0]).AllowMultiple) return t; return null; } if (!((ClassScope)this.type).owner.allowMultiple) return this.type; return null; } private static String GetTypeName(Object t){ Type ty = t as Type; if (ty != null) return ty.FullName; return ((ClassScope)t).GetFullName(); } internal bool IsExpandoAttribute(){ //Use only before partial evaluation has been done Lookup id = this.ctor as Lookup; return id != null && id.Name == "expando"; } internal override AST PartiallyEvaluate(){ this.ctor = this.ctor.PartiallyEvaluateAsCallable(); //first weed out assignment expressions and use them as property initializers ASTList positionalArgs = new ASTList(this.args.context); ASTList namedArgs = new ASTList(this.args.context); for (int i = 0, m = this.args.count; i < m; i++){ AST arg = this.args[i]; Assign assign = arg as Assign; if (assign != null){ assign.rhside = assign.rhside.PartiallyEvaluate(); namedArgs.Append(assign); }else positionalArgs.Append(arg.PartiallyEvaluate()); } int n = positionalArgs.count; IReflect[] argIRs = new IReflect[n]; for (int i = 0; i < n; i++){ AST arg = positionalArgs[i]; // only accept ConstantWrappers if (arg is ConstantWrapper){ Object argument = arg.Evaluate(); if ((argIRs[i] = CustomAttribute.TypeOfArgument(argument)) != null){ this.positionalArgValues.Add(argument); continue; } }else if (arg is ArrayLiteral && ((ArrayLiteral)arg).IsOkToUseInCustomAttribute()){ argIRs[i] = Typeob.ArrayObject; this.positionalArgValues.Add(arg.Evaluate()); continue; } arg.context.HandleError(JSError.InvalidCustomAttributeArgument); return null; // the custom attribute is not good and it will be ignored } //Get the custom attribute and the appropriate constructor (under the covers) this.type = this.ctor.ResolveCustomAttribute(positionalArgs, argIRs, this.target); if (this.type == null) return null; if (Convert.IsPromotableTo((IReflect)this.type, typeof(CodeAccessSecurityAttribute))){ this.context.HandleError(JSError.CannotUseStaticSecurityAttribute); return null; } //Coerce the positional arguments to the right type and supply default values for optional parameters ConstructorInfo c = (ConstructorInfo)((Binding)this.ctor).member; ParameterInfo[] parameters = c.GetParameters(); int j = 0; int len = this.positionalArgValues.Count; foreach (ParameterInfo p in parameters){ IReflect ir = p is ParameterDeclaration ? ((ParameterDeclaration)p).ParameterIReflect : p.ParameterType; if (j < len ){ Object value = this.positionalArgValues[j]; this.positionalArgValues[j] = Convert.Coerce(value, ir, value is ArrayObject); j++; }else{ Object value; if (p.DefaultValue == System.Convert.DBNull){ value = Convert.Coerce(null, ir); }else value = p.DefaultValue; this.positionalArgValues.Add(value); } } //Check validity of property/field initializers for (int i = 0, m = namedArgs.count; i < m; i++){ Assign assign = (Assign)namedArgs[i]; if (assign.lhside is Lookup && (assign.rhside is ConstantWrapper || (assign.rhside is ArrayLiteral && ((ArrayLiteral)assign.rhside).IsOkToUseInCustomAttribute()))){ Object value = assign.rhside.Evaluate(); IReflect argType = null; if (value is ArrayObject || ((argType = CustomAttribute.TypeOfArgument(value)) != null && argType != Typeob.Object)){ String name = ((Lookup)assign.lhside).Name; MemberInfo [] members = ((IReflect)this.type).GetMember(name, BindingFlags.Public|BindingFlags.Instance); if (members == null || members.Length == 0){ assign.context.HandleError(JSError.NoSuchMember); return null; } if (members.Length == 1){ MemberInfo member = members[0]; if (member is FieldInfo){ FieldInfo fieldInfo = (FieldInfo)member; if (!fieldInfo.IsLiteral && !fieldInfo.IsInitOnly){ try{ IReflect ir = fieldInfo is JSVariableField ? ((JSVariableField)fieldInfo).GetInferredType(null) : fieldInfo.FieldType; value = Convert.Coerce(value, ir, value is ArrayObject); this.namedArgFields.Add(member); this.namedArgFieldValues.Add(value); continue; }catch(JScriptException){ assign.rhside.context.HandleError(JSError.TypeMismatch); return null; // the custom attribute is not good and it will be ignored } } }else if (member is PropertyInfo){ PropertyInfo propertyInfo = (PropertyInfo)member; MethodInfo setMethodInfo = JSProperty.GetSetMethod(propertyInfo, false); if (setMethodInfo != null){ ParameterInfo [] paramInfo = setMethodInfo.GetParameters(); if (paramInfo != null && paramInfo.Length == 1){ try{ IReflect ir = paramInfo[0] is ParameterDeclaration ? ((ParameterDeclaration)paramInfo[0]).ParameterIReflect : paramInfo[0].ParameterType; value = Convert.Coerce(value, ir, value is ArrayObject); this.namedArgProperties.Add(member); this.namedArgPropertyValues.Add(value); }catch(JScriptException){ assign.rhside.context.HandleError(JSError.TypeMismatch); return null; // the custom attribute is not good and it will be ignored } continue; } } } } } } assign.context.HandleError(JSError.InvalidCustomAttributeArgument); return null; } if (!this.CheckIfTargetOK(this.type)) return null; //Ignore attribute //Consume and discard assembly name attributes try{ Type ty = this.type as Type; if (ty != null && this.target is AssemblyCustomAttributeList){ if (ty.FullName == "System.Reflection.AssemblyAlgorithmIdAttribute"){ if (this.positionalArgValues.Count > 0) this.Engine.Globals.assemblyHashAlgorithm = (AssemblyHashAlgorithm)Convert.CoerceT(this.positionalArgValues[0], typeof(AssemblyHashAlgorithm)); return null; } if (ty.FullName == "System.Reflection.AssemblyCultureAttribute"){ if (this.positionalArgValues.Count > 0){ String cultureId = Convert.ToString(this.positionalArgValues[0]); if (this.Engine.PEFileKind != PEFileKinds.Dll && cultureId.Length > 0){ this.context.HandleError(JSError.ExecutablesCannotBeLocalized); return null; } this.Engine.Globals.assemblyCulture = new CultureInfo(cultureId); } return null; } if (ty.FullName == "System.Reflection.AssemblyDelaySignAttribute"){ if (this.positionalArgValues.Count > 0) this.Engine.Globals.assemblyDelaySign = Convert.ToBoolean(this.positionalArgValues[0], false); return null; } if (ty.FullName == "System.Reflection.AssemblyFlagsAttribute"){ if (this.positionalArgValues.Count > 0) this.Engine.Globals.assemblyFlags = (AssemblyFlags)(uint)Convert.CoerceT(this.positionalArgValues[0], typeof(uint)); return null; } if (ty.FullName == "System.Reflection.AssemblyKeyFileAttribute"){ if (this.positionalArgValues.Count > 0){ this.Engine.Globals.assemblyKeyFileName = Convert.ToString(this.positionalArgValues[0]); if (this.Engine.Globals.assemblyKeyFileName != null && this.Engine.Globals.assemblyKeyFileName.Length == 0) this.Engine.Globals.assemblyKeyFileName = null; } return null; } if (ty.FullName == "System.Reflection.AssemblyKeyNameAttribute"){ if (this.positionalArgValues.Count > 0){ this.Engine.Globals.assemblyKeyName = Convert.ToString(this.positionalArgValues[0]); if (this.Engine.Globals.assemblyKeyName != null && this.Engine.Globals.assemblyKeyName.Length == 0) this.Engine.Globals.assemblyKeyName = null; } return null; } if (ty.FullName == "System.Reflection.AssemblyVersionAttribute"){ if (this.positionalArgValues.Count > 0) this.Engine.Globals.assemblyVersion = this.ParseVersion(Convert.ToString(this.positionalArgValues[0])); return null; } if (ty.FullName == "System.CLSCompliantAttribute"){ this.Engine.isCLSCompliant = this.args == null || this.args.count == 0 || Convert.ToBoolean(this.positionalArgValues[0], false); return this; } } }catch(ArgumentException){ this.context.HandleError(JSError.InvalidCall); } return this; } private Version ParseVersion(String vString){ ushort major = 1; ushort minor = 0; ushort build = 0; ushort revision = 0; try{ int n = vString.Length; int i = vString.IndexOf('.', 0); if (i < 0) throw new Exception(); major = UInt16.Parse(vString.Substring(0, i), CultureInfo.InvariantCulture); int j = vString.IndexOf('.', i+1); if (j < i+1) minor = UInt16.Parse(vString.Substring(i+1, n-i-1), CultureInfo.InvariantCulture); else{ minor = UInt16.Parse(vString.Substring(i+1, j-i-1), CultureInfo.InvariantCulture); if (vString[j+1] == '*'){ build = CustomAttribute.DaysSince2000(); revision = CustomAttribute.SecondsSinceMidnight(); }else{ int k = vString.IndexOf('.', j+1); if (k < j+1) build = UInt16.Parse(vString.Substring(j+1, n-j-1), CultureInfo.InvariantCulture); else{ build = UInt16.Parse(vString.Substring(j+1, k-j-1), CultureInfo.InvariantCulture); if (vString[k+1] == '*') revision = CustomAttribute.SecondsSinceMidnight(); else revision = UInt16.Parse(vString.Substring(k+1, n-k-1), CultureInfo.InvariantCulture); } } } }catch(Exception){ this.args[0].context.HandleError(JSError.NotValidVersionString); } return new Version(major, minor, build, revision); } private static ushort SecondsSinceMidnight(){ TimeSpan sinceMidnight = DateTime.Now - DateTime.Today; return (ushort)((sinceMidnight.Hours*60*60+sinceMidnight.Minutes*60+sinceMidnight.Seconds)/2); } internal void SetTarget(AST target){ this.target = target; } internal override void TranslateToIL(ILGenerator il, Type rtype){ } internal override void TranslateToILInitializer(ILGenerator il){ } internal static IReflect TypeOfArgument(Object argument){ if (argument is Enum) return argument.GetType(); else if (argument is EnumWrapper) return ((EnumWrapper)argument).type; else{ switch (Convert.GetTypeCode(argument)){ case TypeCode.Empty: case TypeCode.DBNull: return Typeob.Object; case TypeCode.Boolean: return Typeob.Boolean; case TypeCode.Char: return Typeob.Char; case TypeCode.Byte: return Typeob.Byte; case TypeCode.UInt16: return Typeob.UInt16; case TypeCode.UInt32: return Typeob.UInt32; case TypeCode.UInt64: return Typeob.UInt64; case TypeCode.SByte: return Typeob.SByte; case TypeCode.Int16: return Typeob.Int16; case TypeCode.Int32: return Typeob.Int32; case TypeCode.Int64: return Typeob.Int64; case TypeCode.Single: return Typeob.Single; case TypeCode.Double: return Typeob.Double; case TypeCode.Object: if (argument is Type) return Typeob.Type; if (argument is ClassScope) return Typeob.Type; break; case TypeCode.String: return Typeob.String; } return null; } } } }