/* * Copyright (c) 2004-2008 The University of Wroclaw. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the University may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE UNIVERSITY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using Nemerle.Collections; using Nemerle.Utility; using System.IO; using SY = System; using PT = Nemerle.Compiler.Parsetree; using SR = System.Reflection; using SRI = System.Runtime.InteropServices; using SS = System.Security; using SSP = System.Security.Permissions; using SCG = System.Collections.Generic; using Nemerle.Compiler.Typedtree; namespace Nemerle.Compiler { [ManagerAccess] class AttributeCompilerClass { // A security attribute must be imported from an external assembly, // so using LibraryReference.NetType is good enought here. // static is_security_attribute (ti : TypeInfo) : bool { ti is LibraryReference.NetType && typeof(SSP.SecurityAttribute).IsAssignableFrom ((ti :> LibraryReference.NetType).SystemType) } compile_expr (env : GlobalEnv, ti : TypeBuilder, allow_rec : bool, expr : PT.PExpr) : object * MType { match (expr) { | <[ $(x : string) ]> => ((x : object), InternalType.String) | <[ $(x : bool) ]> => ((x : object), InternalType.Boolean) | <[ $(x : char) ]> => ((x : object), InternalType.Char) | PT.PExpr.Literal (Literal.Enum (lval, ty, _)) => def (val, _) = compile_expr (env, ti, allow_rec, PT.PExpr.Literal (lval)); (val, MType.Class (ty, [])) | <[ null ]> => (null, InternalType.Type) | PT.PExpr.Literal (Literal.Decimal) => Message.FatalError (expr.Location, "An attribute argument cannot be number of type decimal"); | PT.PExpr.Literal (lit) => (lit.AsObject (InternalType), lit.GetInternalType (InternalType)) | <[ typeof ($t) ]> when ti != null => match (ti.BindType (t).Fix ()) { | MType.Class (tc, args) => def is_free (a) { !(a is MType) } if (args.Exists (is_free)) { when (!args.ForAll (is_free)) Message.Error ("to create open generic type all arguments must be open `_'"); (tc.SystemType, InternalType.Type) } else (tc.SystemType, InternalType.Type) | _ => Message.FatalError ($"invalid / unbound type `$(t)' in attribute parameter") } | <[ typeof ($t) ]> => match (env.MonoBindType (t)) { | MType.Class (tc, []) => (tc.SystemType, InternalType.Type) | _ => Message.FatalError ($"invalid / unbound type `$(t)' in attribute parameter") } | <[ array [..$elems] ]> when allow_rec => def exprs = List.Map (elems, fun (e) { compile_expr (env, ti, false, e) }); def array_tc = List.FoldLeft (exprs, InternalType.Object_tc, fun (et, curty) { match (et) { | (_, MType.Class (tc, _)) when !tc.Equals (InternalType.Object_tc) => tc | _ => curty } }); def allow_null = array_tc.Equals (InternalType.String_tc) || array_tc.Equals (InternalType.Type_tc); def objects = List.Map (exprs, fun (_) { | (e, MType.Class (tc, _)) when tc.Equals (array_tc) => e | (x, _) when allow_null && x == null => null | (_, t) => Message.FatalError ($ "custom attribute array shall have " "type $array_tc while the element " "has type $t") }); // FIXME: this seems wrong, what if there are two mscorlibs? def arr = objects.ToArray (); (arr : object, MType.Array (MType.Class (array_tc, []), 1)) | <[ $obj . $(n : name) ]> => Message.FatalError ($"unbound / non-enum member $(n.Id) in $obj at custom attribute parameter") | <[ $(n : name) ]> => Message.FatalError ("unbound symbol (or of non-enum type) in custom attribute parameter: " + n.Id) | e => Message.FatalError ($"complex expressions are not allowed in attributes: $e") } } pre_compile (env : GlobalEnv, ti : TypeBuilder, attr : TypeInfo, parms : list [PT.PExpr]) : SR.ConstructorInfo * list[object] * list[SR.PropertyInfo] * list[object] * list[SR.FieldInfo] * list[object] { mutable ctor_parm_types = []; mutable ctor_parms = []; mutable field_infos = []; mutable fields = []; mutable property_infos = []; mutable properties = []; def compile_parm (parm : PT.PExpr) { | <[ $(n : name) = $expr ]> => def name = n.Id; def expr = ConstantFolder.FoldConstants (env, expr); def (obj, ty) = compile_expr (env, ti, true, expr); def problem () { Message.FatalError ("the type " + attr.FullName + " has no field nor property named `" + name + "'") }; def (is_prop, mem) = match (attr.LookupMember (name)) { | [mem] => match (mem.MemberKind) { | MemberKinds.Field => (false, mem) | MemberKinds.Property => (true, mem) | _ => problem () } | _ => problem () }; def handle = mem.GetHandle (); assert (handle != null); if (mem.GetMemType ().Equals (ty.Fix ())) if (is_prop) { property_infos = (handle :> SR.PropertyInfo) :: property_infos; properties = obj :: properties; } else { field_infos = (handle :> SR.FieldInfo) :: field_infos; fields = obj :: fields; } else Message.FatalError ($ "the member `$(name)' has type " "$(mem.GetMemType ()) while the value " "assigned has type $ty") | _ => def (obj, ty) = compile_expr (env, ti, true, parm); ctor_parm_types = ty :: ctor_parm_types; ctor_parms = obj :: ctor_parms; }; List.Iter (parms, compile_parm); ctor_parm_types = List.Rev (ctor_parm_types); mutable proper_ctor = null; /// FIXME: we should use general overloading resolving from tyexpr foreach (meth is IMethod in attr.LookupMember (".ctor")) { def parms = meth.GetParameters (); def check_parm (ty : TyVar, parm : Fun_parm) { ty.TryRequire (parm.ty); } when (ctor_parm_types.Length == parms.Length && List.ForAll2 (ctor_parm_types, parms, check_parm)) { if (proper_ctor == null) proper_ctor = meth.GetConstructorInfo (); else /// FIXME: sometimes it is not ambiguous Message.Error ("ambiguous call to constructor") } } when (proper_ctor == null) Message.FatalError ("none of the constructors of `" + attr.FullName + "' matches positional argument types: " + ctor_parm_types.ToString ()); (proper_ctor, ctor_parms, property_infos, properties, field_infos, fields) } do_compile (env : GlobalEnv, ti : TypeBuilder, attr : TypeInfo, parms : list [PT.PExpr]) : SR.Emit.CustomAttributeBuilder { def (ctor_info, ctor_params, prop_infos, prop_values, field_infos, field_values) = pre_compile (env, ti, attr, parms); SR.Emit.CustomAttributeBuilder (ctor_info, ctor_params.Reverse ().ToArray (), prop_infos.Reverse ().ToArray (), prop_values.Reverse ().ToArray (), field_infos.Reverse ().ToArray (), field_values.Reverse ().ToArray ()) } create_instance (env : GlobalEnv, ti : TypeInfo, parms : list [PT.PExpr]) : object { def (ctor_info, ctor_params, prop_infos, prop_values, field_infos, field_values) = pre_compile (env, null, ti, parms); def obj = ctor_info.Invoke(ctor_params.Reverse ().ToArray ()); List.Iter2 (prop_infos, prop_values, (prop, val) => prop.SetValue (obj, val, null)); List.Iter2 (field_infos, field_values, (field, val) => field.SetValue (obj, val)); obj } internal CompileAttribute (env : GlobalEnv, ti : TypeBuilder, expr : PT.PExpr) : System.AttributeTargets * SR.Emit.CustomAttributeBuilder { def (tc, parms) = CheckAttribute (env, expr); if (is_security_attribute(tc)) (0 :> System.AttributeTargets, null) else (tc.AttributeTargets, do_compile (env, ti, tc, parms)); } internal ResolveAttribute (env : GlobalEnv, expr : PT.PExpr, expect_exact : TypeInfo = null) : option [TypeInfo * list [PT.PExpr]] { def add_end (l, suff : string) { match (l) { | [x] => [x + suff] | x :: xs => x :: add_end (xs, suff) | _ => Util.ice ("empty") } }; match (expr) { | <[ $(_ : name) ]> | <[ $_x . $_y ]> => ResolveAttribute (env, <[ $expr () ]>, expect_exact) | <[ $name ( .. $parms ) ]> => match (Util.QidOfExpr (name)) { | Some ((id, name)) => def is_attribute (t : TypeInfo) { if (expect_exact != null) t.Equals (expect_exact) else t.SuperType (InternalType.Attribute_tc).IsSome } def ctx = name.GetEnv (env); def plain = ctx.LookupType (id); def withattr = ctx.LookupType (add_end (id, "Attribute")); match ((plain, withattr)) { | (Some (t), None) | (None, Some (t)) => if (is_attribute (t)) Some ((t, parms)) else if (expect_exact == null) Message.FatalError ($"`$(t.FullName)' is not an attribute class"); else None () | (Some (t1), Some (t2)) => if (is_attribute (t1)) if (is_attribute (t2)) Message.FatalError ($"ambiguous attribute type name," " it could be `$(t1)' or `$(t2)'"); else Some ((t1, parms)) else if (is_attribute (t2)) Some ((t2, parms)) else if (expect_exact == null) Message.FatalError ($"neither `$(t1)' nor `$(t2)' is an attribute class"); else None () | _ => None () } | _ => None () } | _ => None () } } internal CheckAttribute (env : GlobalEnv, expr : PT.PExpr) : TypeInfo * list [PT.PExpr] { Util.locate (expr.Location, match (ResolveAttribute (env, expr)) { | Some ((t, parms)) => def parms = List.Map (parms, fun (expr) { ConstantFolder.FoldConstants (env, expr); }); (t, parms) | None => Message.FatalError ("the custom attribute `" + PrettyPrint.SprintExpr (None (), expr) + "' could not be found or is invalid") }) } internal GetCompiledAssemblyAttributes (attrs : SCG.List [GlobalEnv * PT.PExpr]) : list [SR.Emit.CustomAttributeBuilder] { mutable result = []; foreach ((env, attr) in attrs) { def (tc, parms) = CheckAttribute (env, attr); unless (tc.Equals (InternalType.AssemblyVersionAttribute_tc) || is_security_attribute (tc)) { result ::= do_compile (env, null, tc, parms); } } result } /* Quote from MSDN: At compile time, attributes convert security declarations to a serialized form in metadata. Declarative security data in metadata is created from the permission that SecurityAttribute::CreatePermission method returns that corresponds to this attribute. This means that the compiler must instantiate a security attribute at compile time and retrieve a serializable permission object to store somewhere in the metadata. */ internal GetPermissionSets (attrs : SCG.IEnumerable [GlobalEnv * PT.PExpr]) : list [SSP.SecurityAction * SS.PermissionSet] { mutable result = []; foreach ((env, attr) in attrs) { def (tc, parms) = CheckAttribute (env, attr); when (is_security_attribute (tc)) { match (create_instance (env, tc, parms)) { | ps is SSP.PermissionSetAttribute => result ::= (ps.Action, ps.CreatePermissionSet ()) | sa is SSP.SecurityAttribute => def perm_set = SS.PermissionSet (SSP.PermissionState.None); _ = perm_set.AddPermission (sa.CreatePermission ()); result ::= (sa.Action, perm_set) | _ => Message.FatalError (attr.Location, "given attribute must be a System.Security.Permissions.SecurityAttribute") } } } result } internal GetPermissionSets (env : GlobalEnv, attrs : SCG.IEnumerable [PT.PExpr]) : list [SSP.SecurityAction * SS.PermissionSet] { GetPermissionSets(attrs.Map(attr => (env, attr))); } internal MakeEmittedAttribute (attr_type : System.Type, value : string) : SR.Emit.CustomAttributeBuilder { MakeEmittedAttribute (attr_type, array [SystemTypeCache.String], value); } internal MakeEmittedAttribute (attr_type : System.Type) : SR.Emit.CustomAttributeBuilder { _ = this; // shut up! def constructor_info = attr_type.GetConstructor (System.Type.EmptyTypes); SR.Emit.CustomAttributeBuilder (constructor_info, array []) } internal MakeEmittedAttribute (attr_type : System.Type, value : int) : SR.Emit.CustomAttributeBuilder { MakeEmittedAttribute (attr_type, array [SystemTypeCache.Int32], value); } internal MakeEmittedAttribute (attr_type : System.Type, param_types : array [System.Type], value : object) : SR.Emit.CustomAttributeBuilder { _ = this; // shut up! def constructor_info = attr_type.GetConstructor (param_types); assert (constructor_info != null); def constructor_params = array [value]; SR.Emit.CustomAttributeBuilder (constructor_info, constructor_params) } internal CheckPInvoking (meth : MethodBuilder, tb : SR.Emit.TypeBuilder, attrs : SR.MethodAttributes, parm_types_array : array [SY.Type]) : SR.Emit.MethodBuilder { def loop (_) { | expr :: rest => def env = meth.DeclaringType.GlobalEnv; match (ResolveAttribute (env, expr)) { | Some ((tc, <[ $(dll_name : string) ]> :: parms)) when tc.Equals (InternalType.DllImport_tc) => when (meth.Attributes & NemerleAttributes.Extern == 0) Message.Error (expr.Location, "only methods marked with `extern' modifier can have " "`System.Runtime.InteropServices.DllImport' attribute"); mutable callingconv = SRI.CallingConvention.Winapi; mutable charset = SRI.CharSet.Ansi; mutable preserve_sig = true; mutable entry_point = meth.Name; mutable best_fit_mapping = false; mutable throw_on_unmappable = false; mutable best_fit_mapping_set = false; mutable throw_on_unmappable_set = false; mutable set_best_fit = null; mutable set_throw_on = null; mutable char_set_extra = 0; foreach (p in parms) match (p) { | <[ $(target : dyn) = $val ]> => match ((target, ConstantFolder.FoldConstants (env, val))) { | ("BestFitMapping", <[ $(val : bool) ]>) => best_fit_mapping = val; best_fit_mapping_set = true; | ("CallingConvention", PT.PExpr.Literal (Literal.Enum (l, _, _))) => callingconv = l.AsObject (InternalType) :> SRI.CallingConvention; | ("CharSet", PT.PExpr.Literal (Literal.Enum (l, _, _))) => charset = l.AsObject (InternalType) :> SRI.CharSet; | ("EntryPoint", <[ $(val : string) ]>) => entry_point = val; | ("ExactSpelling", _) => char_set_extra |= 0x01; | ("PreserveSig", <[ $(val : bool) ]>) => preserve_sig = val; | ("SetLastError", _) => char_set_extra |= 0x40; | ("ThrowOnUnmappableChar", <[ $(val : bool) ]>) => throw_on_unmappable = val; throw_on_unmappable_set = true; | (name, val) => Message.Error (val.Location, $"value is not valid for parameter $name") } | _ => Message.Error (p.Location, "unnamed DllImport parameter") } charset |= (char_set_extra :> SRI.CharSet); when (throw_on_unmappable_set || best_fit_mapping_set) { set_best_fit = typeof (SR.Emit.MethodBuilder).GetMethod ("set_BestFitMapping", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); set_throw_on = typeof (SR.Emit.MethodBuilder).GetMethod ("set_ThrowOnUnmappableChar", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); when ((set_best_fit == null) || (set_throw_on == null)) { Message.Error ("The ThrowOnUnmappableChar and BestFitMapping" " attributes can only be emitted when running on the mono runtime."); } } def mb = tb.DefinePInvokeMethod (meth.Name, dll_name, entry_point, attrs | SR.MethodAttributes.HideBySig | SR.MethodAttributes.PinvokeImpl, SR.CallingConventions.Standard, meth.ReturnType.SystemType, parm_types_array, callingconv, charset); when (preserve_sig) mb.SetImplementationFlags (SR.MethodImplAttributes.PreserveSig); when (throw_on_unmappable_set) _ = set_throw_on.Invoke (mb, SR.BindingFlags.Default, null, array [ throw_on_unmappable : object], null); when (best_fit_mapping_set) _ = set_best_fit.Invoke (mb, SR.BindingFlags.Default, null, array [ best_fit_mapping : object], null); meth.GetModifiers ().custom_attrs = List.Filter (meth.GetModifiers ().custom_attrs, fun (x) { x : object != expr }); mb | _ => loop (rest) } | [] => null } loop (meth.GetModifiers ().GetCustomAttributes ()) } internal this (man : ManagerClass) { Manager = man; } } public partial class TypesManager { static read_keypair (loc : Location, name : string) : SR.StrongNameKeyPair { try { SR.StrongNameKeyPair(File.Open(name, FileMode.Open, FileAccess.Read)) } catch { | _ is DirectoryNotFoundException => Message.FatalError (loc, "could not find directory of `" + name + "' with key pair for assembly") | _ is FileNotFoundException => Message.FatalError (loc, "could not find file `" + name + "' with key pair for assembly") } } public AddAssemblyAttribute (env : GlobalEnv, attr : Parsetree.PExpr) : void { def add (phase) { def suff = AttributeMacroExpansion.Suffix (MacroTargets.Assembly, phase); match (MacroRegistry.lookup_macro (env, attr, suff)) { | None => false | Some => def expansion = AssemblyAttributeMacroExpansion (MacroTargets.Assembly, phase, attr, [], null, null, env); AddMacroExpansion (expansion); true } } def b1 = add (MacroPhase.BeforeInheritance); def b2 = add (MacroPhase.BeforeTypedMembers); def b3 = add (MacroPhase.WithTypedMembers); if (b1 || b2 || b3) () else assembly_attributes.Add (env, attr); } internal CreateAssemblyName () : SR.AssemblyName { /* create an assembly name and set its properties according to defined global assembly attributes */ def an = SR.AssemblyName (); an.CodeBase = string.Concat("file:///", Directory.GetCurrentDirectory()); when (Manager.Options.StrongAssemblyKeyName != null) { an.KeyPair = read_keypair (Location.Default, Manager.Options.StrongAssemblyKeyName); } foreach ((env, attr) in assembly_attributes) { /* store resolved attribute */ def (tc, parms) = Manager.AttributeCompiler.CheckAttribute (env, attr); def take_string (pars) { | [ <[ $(x : string) ]> ] => x | _ => Message.FatalError (attr.Location, "given attribute must have single string as parameter") } if (tc.Equals (InternalType.AssemblyVersionAttribute_tc)) { // spec for parsing version is quite interesting // http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemreflectionassemblyversionattributeclassctortopic.asp def ver = NString.Split (take_string (parms), array ['.']); mutable version_object = null; try { def verint = List.Map (ver, fun (x) { if (x == "*") -1 else (SY.UInt16.Parse (x) :> int) }); version_object = match (verint) { | [x1] => SY.Version (x1.ToString ()) | [x1, x2] => SY.Version (x1, x2) | [x1, x2, -1] => def spanBuild = SY.DateTime.Now.Subtract (SY.DateTime (2000, 1, 1)); def spanRevision = SY.DateTime.Now.Subtract (SY.DateTime.Today); SY.Version (x1, x2, spanBuild.Days, (spanRevision.Ticks / 20000000) :> int) | [x1, x2, x3] => SY.Version (x1, x2, x3) | [x1, x2, x3, -1] => def spanRevision = SY.DateTime.Now.Subtract (SY.DateTime.Today); SY.Version (x1, x2, x3, (spanRevision.Ticks / 20000000) :> int) | [x1, x2, x3, x4] => SY.Version (x1, x2, x3, x4) | _ => Message.Error (attr.Location, "invalid format of version attribute"); SY.Version (); } } catch { | _ is SY.OverflowException => Message.Error (attr.Location, "wrong format of version attribute"); version_object = SY.Version (); } an.Version = version_object; } else if (tc.Equals (InternalType.AssemblyKeyFileAttribute_tc)) { def key = take_string (parms); if (an.KeyPair != null) Message.Warning (attr.Location, "AssemblyKeyFile attribute will be ignored, as key file was already specified") else when (key != "") an.KeyPair = read_keypair (attr.Location, key); } else when (tc.Equals (InternalType.AssemblyCultureAttribute_tc)) an.CultureInfo = SY.Globalization.CultureInfo (take_string (parms)); }; an } } public partial class Modifiers { public FindAttributeWithArgs (looking_for : TypeInfo, env : GlobalEnv) : option [PT.PExpr * list [PT.PExpr]] { // find our attribute def loop (_) { | expr :: exprs => match (env.Manager.AttributeCompiler.ResolveAttribute (env, expr, looking_for)) { | Some ((_, args)) => Some ((expr, args)) | _ => loop (exprs) } | _ => None () } loop (custom_attrs) } public AttributeTypes(env : GlobalEnv) : list[TypeInfo] { def loop (_) { | expr :: exprs => match (env.Manager.AttributeCompiler.ResolveAttribute (env, expr)) { | Some ((ti, _)) => ti :: loop(exprs) | _ => loop (exprs) } | _ => [] } loop (custom_attrs) } public FindAttribute (looking_for : TypeInfo, env : GlobalEnv) : option [PT.PExpr] { // find our attribute def loop (_) { | expr :: exprs => match (env.Manager.AttributeCompiler.ResolveAttribute (env, expr, looking_for)) { | Some => Some (expr) | _ => loop (exprs) } | _ => None () } loop (custom_attrs) } internal SaveCustomAttributes (ti : TypeBuilder, adder : SY.AttributeTargets * SR.Emit.CustomAttributeBuilder -> string) : void { foreach (expr in custom_attrs) { try { def error = adder (ti.Manager.AttributeCompiler.CompileAttribute (ti.GlobalEnv, ti, expr)); when (error != null) Message.Error ($"custom attribute $expr is not valid on " + error); } catch { | _ is Recovery => () } }; foreach ((suff, expr) in macro_attrs) { try { def (m, parms) = match (MacroRegistry.lookup_macro (ti.GlobalEnv, expr, suff)) { | Some ((_, m, parms)) => (m, parms) | _ => Util.ice ("macro is not a macro?" + PrettyPrint.SprintExpr (None (), expr)) }; // check if macro needs to be saved in metadata // it should be done only if it will be inherited in some derived class def inherited = m.IsInherited && !ti.IsSealed; when (inherited) { def concatenated = parms.ToString ("@"); // def _x = ti.env.GetMacroContext (); def name = m.GetNamespace () + "." + m.GetName (); def serialized = <[ Nemerle.Internal.MacroAttribute ($(name : string), // $(ti.env.GetMacroContext () : int), 0, $(concatenated : string)) ]>; def error = adder (ti.Manager.AttributeCompiler.CompileAttribute (ti.GlobalEnv, ti, serialized)); when (error != null) Message.Error ($"macro attribute $name is not valid on " + error); } } catch { | _ is Recovery => () } }; } } }