1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.Xml;
8 using System.Xml.Schema;
9 using System.Reflection;
10 using System.Globalization;
11 using System.Diagnostics;
12 
13 namespace System.Xml.Xsl.Runtime
14 {
15     /// <summary>
16     /// Table of bound extension functions.  Once an extension function is bound and entered into the table, future bindings
17     /// will be very fast.  This table is not thread-safe.
18     /// </summary>
19     internal class XmlExtensionFunctionTable
20     {
21         private Dictionary<XmlExtensionFunction, XmlExtensionFunction> _table;
22         private XmlExtensionFunction _funcCached;
23 
XmlExtensionFunctionTable()24         public XmlExtensionFunctionTable()
25         {
26             _table = new Dictionary<XmlExtensionFunction, XmlExtensionFunction>();
27         }
28 
Bind(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)29         public XmlExtensionFunction Bind(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)
30         {
31             XmlExtensionFunction func;
32 
33             if (_funcCached == null)
34                 _funcCached = new XmlExtensionFunction();
35 
36             // If the extension function already exists in the table, then binding has already been performed
37             _funcCached.Init(name, namespaceUri, numArgs, objectType, flags);
38             if (!_table.TryGetValue(_funcCached, out func))
39             {
40                 // Function doesn't exist, so bind it and enter it into the table
41                 func = _funcCached;
42                 _funcCached = null;
43 
44                 func.Bind();
45                 _table.Add(func, func);
46             }
47 
48             return func;
49         }
50     }
51 
52     /// <summary>
53     /// This internal class contains methods that allow binding to extension functions and invoking them.
54     /// </summary>
55     internal class XmlExtensionFunction
56     {
57         private string _namespaceUri;                // Extension object identifier
58         private string _name;                        // Name of this method
59         private int _numArgs;                        // Argument count
60         private Type _objectType;                    // Type of the object which will be searched for matching methods
61         private BindingFlags _flags;                 // Modifiers that were used to search for a matching signature
62         private int _hashCode;                       // Pre-computed hashcode
63 
64         private MethodInfo _meth;                    // MethodInfo for extension function
65         private Type[] _argClrTypes;                 // Type array for extension function arguments
66         private Type _retClrType;                    // Type for extension function return value
67         private XmlQueryType[] _argXmlTypes;         // XmlQueryType array for extension function arguments
68         private XmlQueryType _retXmlType;            // XmlQueryType for extension function return value
69 
70         /// <summary>
71         /// Constructor.
72         /// </summary>
XmlExtensionFunction()73         public XmlExtensionFunction()
74         {
75         }
76 
77         /// <summary>
78         /// Constructor (directly binds to passed MethodInfo).
79         /// </summary>
XmlExtensionFunction(string name, string namespaceUri, MethodInfo meth)80         public XmlExtensionFunction(string name, string namespaceUri, MethodInfo meth)
81         {
82             _name = name;
83             _namespaceUri = namespaceUri;
84             Bind(meth);
85         }
86 
87         /// <summary>
88         /// Constructor.
89         /// </summary>
XmlExtensionFunction(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)90         public XmlExtensionFunction(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)
91         {
92             Init(name, namespaceUri, numArgs, objectType, flags);
93         }
94 
95         /// <summary>
96         /// Initialize, but do not bind.
97         /// </summary>
Init(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)98         public void Init(string name, string namespaceUri, int numArgs, Type objectType, BindingFlags flags)
99         {
100             _name = name;
101             _namespaceUri = namespaceUri;
102             _numArgs = numArgs;
103             _objectType = objectType;
104             _flags = flags;
105             _meth = null;
106             _argClrTypes = null;
107             _retClrType = null;
108             _argXmlTypes = null;
109             _retXmlType = null;
110 
111             // Compute hash code so that it is not recomputed each time GetHashCode() is called
112             _hashCode = namespaceUri.GetHashCode() ^ name.GetHashCode() ^ ((int)flags << 16) ^ (int)numArgs;
113         }
114 
115         /// <summary>
116         /// Once Bind has been successfully called, Method will be non-null.
117         /// </summary>
118         public MethodInfo Method
119         {
120             get { return _meth; }
121         }
122 
123         /// <summary>
124         /// Once Bind has been successfully called, the Clr type of each argument can be accessed.
125         /// Note that this may be different than Method.GetParameterInfo().ParameterType.
126         /// </summary>
GetClrArgumentType(int index)127         public Type GetClrArgumentType(int index)
128         {
129             return _argClrTypes[index];
130         }
131 
132         /// <summary>
133         /// Once Bind has been successfully called, the Clr type of the return value can be accessed.
134         /// Note that this may be different than Method.GetParameterInfo().ReturnType.
135         /// </summary>
136         public Type ClrReturnType
137         {
138             get { return _retClrType; }
139         }
140 
141         /// <summary>
142         /// Once Bind has been successfully called, the inferred Xml types of the arguments can be accessed.
143         /// </summary>
GetXmlArgumentType(int index)144         public XmlQueryType GetXmlArgumentType(int index)
145         {
146             return _argXmlTypes[index];
147         }
148 
149         /// <summary>
150         /// Once Bind has been successfully called, the inferred Xml type of the return value can be accessed.
151         /// </summary>
152         public XmlQueryType XmlReturnType
153         {
154             get { return _retXmlType; }
155         }
156 
157         /// <summary>
158         /// Return true if the CLR type specified in the Init() call has a matching method.
159         /// </summary>
CanBind()160         public bool CanBind()
161         {
162             MethodInfo[] methods = _objectType.GetMethods(_flags);
163             bool ignoreCase = (_flags & BindingFlags.IgnoreCase) != 0;
164             StringComparison comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
165 
166             // Find method in object type
167             foreach (MethodInfo methSearch in methods)
168             {
169                 if (methSearch.Name.Equals(_name, comparison) && (_numArgs == -1 || methSearch.GetParameters().Length == _numArgs))
170                 {
171                     // Binding to generic methods will never succeed
172                     if (!methSearch.IsGenericMethodDefinition)
173                         return true;
174                 }
175             }
176 
177             return false;
178         }
179 
180         /// <summary>
181         /// Bind to the CLR type specified in the Init() call.  If a matching method cannot be found, throw an exception.
182         /// </summary>
Bind()183         public void Bind()
184         {
185             MethodInfo[] methods = _objectType.GetMethods(_flags);
186             MethodInfo methMatch = null;
187             bool ignoreCase = (_flags & BindingFlags.IgnoreCase) != 0;
188             StringComparison comparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
189 
190             // Find method in object type
191             foreach (MethodInfo methSearch in methods)
192             {
193                 if (methSearch.Name.Equals(_name, comparison) && (_numArgs == -1 || methSearch.GetParameters().Length == _numArgs))
194                 {
195                     if (methMatch != null)
196                         throw new XslTransformException(/*[XT_037]*/SR.XmlIl_AmbiguousExtensionMethod, _namespaceUri, _name, _numArgs.ToString(CultureInfo.InvariantCulture));
197 
198                     methMatch = methSearch;
199                 }
200             }
201 
202             if (methMatch == null)
203             {
204                 methods = _objectType.GetMethods(_flags | BindingFlags.NonPublic);
205                 foreach (MethodInfo methSearch in methods)
206                 {
207                     if (methSearch.Name.Equals(_name, comparison) && methSearch.GetParameters().Length == _numArgs)
208                         throw new XslTransformException(/*[XT_038]*/SR.XmlIl_NonPublicExtensionMethod, _namespaceUri, _name);
209                 }
210                 throw new XslTransformException(/*[XT_039]*/SR.XmlIl_NoExtensionMethod, _namespaceUri, _name, _numArgs.ToString(CultureInfo.InvariantCulture));
211             }
212 
213             if (methMatch.IsGenericMethodDefinition)
214                 throw new XslTransformException(/*[XT_040]*/SR.XmlIl_GenericExtensionMethod, _namespaceUri, _name);
215 
216             Debug.Assert(methMatch.ContainsGenericParameters == false);
217 
218             Bind(methMatch);
219         }
220 
221         /// <summary>
222         /// Bind to the specified MethodInfo.
223         /// </summary>
Bind(MethodInfo meth)224         private void Bind(MethodInfo meth)
225         {
226             ParameterInfo[] paramInfo = meth.GetParameters();
227             int i;
228 
229             // Save the MethodInfo
230             _meth = meth;
231 
232             // Get the Clr type of each parameter
233             _argClrTypes = new Type[paramInfo.Length];
234             for (i = 0; i < paramInfo.Length; i++)
235                 _argClrTypes[i] = GetClrType(paramInfo[i].ParameterType);
236 
237             // Get the Clr type of the return value
238             _retClrType = GetClrType(_meth.ReturnType);
239 
240             // Infer an Xml type for each Clr type
241             _argXmlTypes = new XmlQueryType[paramInfo.Length];
242             for (i = 0; i < paramInfo.Length; i++)
243             {
244                 _argXmlTypes[i] = InferXmlType(_argClrTypes[i]);
245 
246                 // BUGBUG:
247                 // 1. A couple built-in Xslt functions  allow Rtf as argument, which is
248                 //    different from what InferXmlType returns.  Until XsltEarlyBound references
249                 //    a Qil function, we'll work around this case by assuming that all built-in
250                 //    Xslt functions allow Rtf.
251                 // 2. Script arguments should allow node-sets which are not statically known
252                 //    to be Dod to be passed, so relax static typing in this case.
253                 if (_namespaceUri.Length == 0)
254                 {
255                     if ((object)_argXmlTypes[i] == (object)XmlQueryTypeFactory.NodeNotRtf)
256                         _argXmlTypes[i] = XmlQueryTypeFactory.Node;
257                     else if ((object)_argXmlTypes[i] == (object)XmlQueryTypeFactory.NodeSDod)
258                         _argXmlTypes[i] = XmlQueryTypeFactory.NodeS;
259                 }
260                 else
261                 {
262                     if ((object)_argXmlTypes[i] == (object)XmlQueryTypeFactory.NodeSDod)
263                         _argXmlTypes[i] = XmlQueryTypeFactory.NodeNotRtfS;
264                 }
265             }
266 
267             // Infer an Xml type for the return Clr type
268             _retXmlType = InferXmlType(_retClrType);
269         }
270 
271         /// <summary>
272         /// Convert the incoming arguments to an array of CLR objects, and then invoke the external function on the "extObj" object instance.
273         /// </summary>
Invoke(object extObj, object[] args)274         public object Invoke(object extObj, object[] args)
275         {
276             Debug.Assert(_meth != null, "Must call Bind() before calling Invoke.");
277             Debug.Assert(args.Length == _argClrTypes.Length, "Mismatched number of actual and formal arguments.");
278 
279             try
280             {
281                 return _meth.Invoke(extObj, args);
282             }
283             catch (TargetInvocationException e)
284             {
285                 throw new XslTransformException(e.InnerException, SR.XmlIl_ExtensionError, _name);
286             }
287             catch (Exception e)
288             {
289                 if (!XmlException.IsCatchableException(e))
290                 {
291                     throw;
292                 }
293                 throw new XslTransformException(e, SR.XmlIl_ExtensionError, _name);
294             }
295         }
296 
297         /// <summary>
298         /// Return true if this XmlExtensionFunction has the same values as another XmlExtensionFunction.
299         /// </summary>
Equals(object other)300         public override bool Equals(object other)
301         {
302             XmlExtensionFunction that = other as XmlExtensionFunction;
303             Debug.Assert(that != null);
304 
305             // Compare name, argument count, object type, and binding flags
306             return (_hashCode == that._hashCode && _name == that._name && _namespaceUri == that._namespaceUri &&
307                     _numArgs == that._numArgs && _objectType == that._objectType && _flags == that._flags);
308         }
309 
310         /// <summary>
311         /// Return this object's hash code, previously computed for performance.
312         /// </summary>
GetHashCode()313         public override int GetHashCode()
314         {
315             return _hashCode;
316         }
317 
318         /// <summary>
319         /// 1. Map enumerations to the underlying integral type.
320         /// 2. Throw an exception if the type is ByRef
321         /// </summary>
GetClrType(Type clrType)322         private Type GetClrType(Type clrType)
323         {
324             if (clrType.IsEnum)
325                 return Enum.GetUnderlyingType(clrType);
326 
327             if (clrType.IsByRef)
328                 throw new XslTransformException(/*[XT_050]*/SR.XmlIl_ByRefType, _namespaceUri, _name);
329 
330             return clrType;
331         }
332 
333         /// <summary>
334         /// Infer an Xml type from a Clr type using Xslt inference rules
335         /// </summary>
InferXmlType(Type clrType)336         private XmlQueryType InferXmlType(Type clrType)
337         {
338             return XsltConvert.InferXsltType(clrType);
339         }
340     }
341 }
342