Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / Xslt / Scripts.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="Scripts.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <spec>http://devdiv/Documents/Whidbey/CLR/CurrentSpecs/BCL/CodeDom%20Activation.doc</spec>
7 //------------------------------------------------------------------------------
8
9 using System.CodeDom;
10 using System.CodeDom.Compiler;
11 using System.Collections.Generic;
12 using System.Collections.Specialized;
13 using System.Configuration;
14 using System.Diagnostics;
15 using System.Globalization;
16 using System.IO;
17 using System.Reflection;
18 using System.Runtime.InteropServices;
19 using System.Text.RegularExpressions;
20 using System.Security.Permissions;
21 using System.Threading;
22 using System.Xml.Xsl.IlGen;
23 using System.Xml.Xsl.Runtime;
24 using System.Runtime.Versioning;
25
26 namespace System.Xml.Xsl.Xslt {
27     using Res = System.Xml.Utils.Res;
28
29     internal class ScriptClass {
30         public string               ns;
31         public CompilerInfo         compilerInfo;
32         public StringCollection     refAssemblies;
33         public StringCollection     nsImports;
34         public CodeTypeDeclaration  typeDecl;
35         public bool                 refAssembliesByHref;
36
37         public Dictionary<string, string> scriptUris;
38
39         // These two fields are used to report a compile error when its position is outside
40         // of all user code snippets in the generated temporary file
41         public string               endUri;
42         public Location             endLoc;
43
44         public ScriptClass(string ns, CompilerInfo compilerInfo) {
45             this.ns             = ns;
46             this.compilerInfo   = compilerInfo;
47             this.refAssemblies  = new StringCollection();
48             this.nsImports      = new StringCollection();
49             this.typeDecl       = new CodeTypeDeclaration(GenerateUniqueClassName());
50             this.refAssembliesByHref = false;
51             this.scriptUris     = new Dictionary<string, string>(
52 #if !FEATURE_CASE_SENSITIVE_FILESYSTEM            
53                 StringComparer.OrdinalIgnoreCase
54 #endif
55             );
56         }
57
58         private static long scriptClassCounter = 0;
59
60         private static string GenerateUniqueClassName() {
61             return "Script" + Interlocked.Increment(ref scriptClassCounter);
62         }
63
64         public void AddScriptBlock(string source, string uriString, int lineNumber, Location end) {
65             CodeSnippetTypeMember scriptSnippet = new CodeSnippetTypeMember(source);
66             string fileName = SourceLineInfo.GetFileName(uriString);
67             if (lineNumber > 0) {
68                 scriptSnippet.LinePragma = new CodeLinePragma(fileName, lineNumber);
69                 scriptUris[fileName] = uriString;
70             }
71             typeDecl.Members.Add(scriptSnippet);
72
73             this.endUri = uriString;
74             this.endLoc = end;
75         }
76
77         public ISourceLineInfo EndLineInfo {
78             get {
79                 return new SourceLineInfo(this.endUri, this.endLoc, this.endLoc);
80             }
81         }
82     }
83
84     internal class Scripts {
85         private const string ScriptClassesNamespace = "System.Xml.Xsl.CompiledQuery";
86
87         private Compiler                  compiler;
88         private List<ScriptClass>         scriptClasses = new List<ScriptClass>();
89         private Dictionary<string, Type>  nsToType      = new Dictionary<string, Type>();
90         private XmlExtensionFunctionTable extFuncs      = new XmlExtensionFunctionTable();
91
92         public Scripts(Compiler compiler) {
93             this.compiler = compiler;
94         }
95
96         public Dictionary<string, Type> ScriptClasses {
97             get { return nsToType; }
98         }
99
100         public XmlExtensionFunction ResolveFunction(string name, string ns, int numArgs, IErrorHelper errorHelper) {
101             Type type;
102             if (nsToType.TryGetValue(ns, out type)) {
103                 try {
104                     return extFuncs.Bind(name, ns, numArgs, type, XmlQueryRuntime.EarlyBoundFlags);
105                 }
106                 catch (XslTransformException e) {
107                     errorHelper.ReportError(e.Message);
108                 }
109             }
110             return null;
111         }
112
113         public ScriptClass GetScriptClass(string ns, string language, IErrorHelper errorHelper) {
114 #if CONFIGURATION_DEP
115             CompilerInfo compilerInfo;
116             try {
117                 compilerInfo = CodeDomProvider.GetCompilerInfo(language);
118                 Debug.Assert(compilerInfo != null);
119             }
120             catch (ConfigurationException) {
121                 // There is no CodeDom provider defined for this language
122                 errorHelper.ReportError(/*[XT_010]*/Res.Xslt_ScriptInvalidLanguage, language);
123                 return null;
124             }
125
126             foreach (ScriptClass scriptClass in scriptClasses) {
127                 if (ns == scriptClass.ns) {
128                     // Use object comparison because CompilerInfo.Equals may throw
129                     if (compilerInfo != scriptClass.compilerInfo) {
130                         errorHelper.ReportError(/*[XT_011]*/Res.Xslt_ScriptMixedLanguages, ns);
131                         return null;
132                     }
133                     return scriptClass;
134                 }
135             }
136
137             ScriptClass newScriptClass = new ScriptClass(ns, compilerInfo);
138             newScriptClass.typeDecl.TypeAttributes = TypeAttributes.Public;
139             scriptClasses.Add(newScriptClass);
140             return newScriptClass;
141 #else
142             return null;
143 #endif
144         }
145
146         //------------------------------------------------
147         // Compilation
148         //------------------------------------------------
149
150         public void CompileScripts() {
151             List<ScriptClass> scriptsForLang = new List<ScriptClass>();
152
153             for (int i = 0; i < scriptClasses.Count; i++) {
154                 // If the script is already compiled, skip it
155                 if (scriptClasses[i] == null)
156                     continue;
157
158                 // Group together scripts with the same CompilerInfo
159                 CompilerInfo compilerInfo = scriptClasses[i].compilerInfo;
160                 scriptsForLang.Clear();
161
162                 for (int j = i; j < scriptClasses.Count; j++) {
163                     // Use object comparison because CompilerInfo.Equals may throw
164                     if (scriptClasses[j] != null && scriptClasses[j].compilerInfo == compilerInfo) {
165                         scriptsForLang.Add(scriptClasses[j]);
166                         scriptClasses[j] = null;
167                     }
168                 }
169
170                 Assembly assembly = CompileAssembly(scriptsForLang);
171
172                 if (assembly != null) {
173                     foreach (ScriptClass script in scriptsForLang) {
174                         Type clrType = assembly.GetType(ScriptClassesNamespace + Type.Delimiter + script.typeDecl.Name);
175                         if (clrType != null) {
176                             nsToType.Add(script.ns, clrType);
177                         }
178                     }
179                 }
180             }
181         }
182
183         // Namespaces we always import when compiling
184         private static readonly string[] defaultNamespaces = new string[] {
185             "System",
186             "System.Collections",
187             "System.Text",
188             "System.Text.RegularExpressions",
189             "System.Xml",
190             "System.Xml.Xsl",
191             "System.Xml.XPath",
192         };
193
194         // SxS: This method does not take any resource name and does not expose any resources to the caller.
195         // It's OK to suppress the SxS warning.
196         [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
197         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
198         [ResourceExposure(ResourceScope.None)]
199         private Assembly CompileAssembly(List<ScriptClass> scriptsForLang) {
200             TempFileCollection      allTempFiles  = compiler.CompilerResults.TempFiles;
201             CompilerErrorCollection allErrors     = compiler.CompilerResults.Errors;
202             ScriptClass             lastScript    = scriptsForLang[scriptsForLang.Count - 1];
203             CodeDomProvider         provider;
204             bool                    isVB          = false;
205
206             try {
207                 provider = lastScript.compilerInfo.CreateProvider();
208             }
209             catch (ConfigurationException e) {
210                 // The CodeDom provider type could not be located, or some error in machine.config
211                 allErrors.Add(compiler.CreateError(lastScript.EndLineInfo, /*[XT_041]*/Res.Xslt_ScriptCompileException, e.Message));
212                 return null;
213             }
214
215 #if !FEATURE_PAL // visualbasic
216             isVB = provider is Microsoft.VisualBasic.VBCodeProvider;
217 #endif // !FEATURE_PAL
218
219             CodeCompileUnit[] codeUnits = new CodeCompileUnit[scriptsForLang.Count];
220             CompilerParameters compilParams = lastScript.compilerInfo.CreateDefaultCompilerParameters();
221
222             // 
223
224
225             compilParams.ReferencedAssemblies.Add(typeof(System.Xml.Res).Assembly.Location);
226             compilParams.ReferencedAssemblies.Add("System.dll");
227             if (isVB) {
228                 compilParams.ReferencedAssemblies.Add("Microsoft.VisualBasic.dll");
229             }
230
231             bool refAssembliesByHref = false;
232
233             for (int idx = 0; idx < scriptsForLang.Count; idx++) {
234                 ScriptClass script = scriptsForLang[idx];
235                 CodeNamespace scriptNs = new CodeNamespace(ScriptClassesNamespace);
236
237                 // Add imported namespaces
238                 foreach (string ns in defaultNamespaces) {
239                     scriptNs.Imports.Add(new CodeNamespaceImport(ns));
240                 }
241                 if (isVB) {
242                     scriptNs.Imports.Add(new CodeNamespaceImport("Microsoft.VisualBasic"));
243                 }
244                 foreach (string ns in script.nsImports) {
245                     scriptNs.Imports.Add(new CodeNamespaceImport(ns));
246                 }
247
248                 scriptNs.Types.Add(script.typeDecl);
249
250                 CodeCompileUnit unit = new CodeCompileUnit(); {
251                     unit.Namespaces.Add(scriptNs);
252
253                     if (isVB) {
254                         // This settings have sense for Visual Basic only. In future releases we may allow to specify
255                         // them explicitly in the msxsl:script element.
256                         unit.UserData["AllowLateBound"]             = true;   // Allow variables to be declared untyped
257                         unit.UserData["RequireVariableDeclaration"] = false;  // Allow variables to be undeclared
258                     }
259
260                     // Put SecurityTransparentAttribute and SecurityRulesAttribute on the first CodeCompileUnit only
261                     if (idx == 0) {
262                         unit.AssemblyCustomAttributes.Add(new CodeAttributeDeclaration("System.Security.SecurityTransparentAttribute"));
263
264                         // We want the assemblies generated for scripts to stick to the old security model
265                         unit.AssemblyCustomAttributes.Add(
266                             new CodeAttributeDeclaration(
267                                 new CodeTypeReference(typeof(System.Security.SecurityRulesAttribute)),
268                                 new CodeAttributeArgument(
269                                     new CodeFieldReferenceExpression(
270                                         new CodeTypeReferenceExpression(typeof(System.Security.SecurityRuleSet)), "Level1"))));
271                     }
272                 }
273
274                 codeUnits[idx] = unit;
275                 foreach (string name in script.refAssemblies) {
276                     compilParams.ReferencedAssemblies.Add(name);
277                 }
278
279                 refAssembliesByHref |= script.refAssembliesByHref;
280             }
281
282             XsltSettings settings                 = compiler.Settings;
283             compilParams.WarningLevel             = settings.WarningLevel >= 0 ? settings.WarningLevel : compilParams.WarningLevel;
284             compilParams.TreatWarningsAsErrors    = settings.TreatWarningsAsErrors;
285             compilParams.IncludeDebugInformation  = compiler.IsDebug;
286
287             string asmPath = compiler.ScriptAssemblyPath;
288             if (asmPath != null && scriptsForLang.Count < scriptClasses.Count) {
289                 asmPath = Path.ChangeExtension(asmPath, "." + GetLanguageName(lastScript.compilerInfo) + Path.GetExtension(asmPath));
290             }
291             compilParams.OutputAssembly           = asmPath;
292
293             string tempDir = (settings.TempFiles != null) ? settings.TempFiles.TempDir : null;
294             compilParams.TempFiles                = new TempFileCollection(tempDir);
295
296             // We need only .dll and .pdb, but there is no way to specify that
297             bool keepFiles = (compiler.IsDebug && asmPath == null);
298         #if DEBUG 
299             keepFiles = keepFiles || XmlILTrace.IsEnabled;
300         #endif
301             keepFiles = keepFiles && !settings.CheckOnly;
302
303
304             compilParams.TempFiles.KeepFiles = keepFiles;
305
306             // If GenerateInMemory == true, then CodeDom loads the compiled assembly using Assembly.Load(byte[])
307             // instead of Assembly.Load(AssemblyName).  That means the assembly will be loaded in the anonymous
308             // context (http://blogs.msdn.com/Microsoft/archive/2003/05/29/57143.aspx), and its dependencies can only
309             // be loaded from the Load context or using AssemblyResolve event.  However we want to use the LoadFrom
310             // context to preload all dependencies specified by <ms:assembly href="uri-reference"/>, so we turn off
311             // GenerateInMemory here.
312             compilParams.GenerateInMemory = (asmPath == null && !compiler.IsDebug && !refAssembliesByHref) || settings.CheckOnly;
313
314             CompilerResults results;
315
316             try {
317                 results = provider.CompileAssemblyFromDom(compilParams, codeUnits);
318             }
319             catch (ExternalException e) {
320                 // Compiler might have created temporary files
321                 results = new CompilerResults(compilParams.TempFiles);
322                 results.Errors.Add(compiler.CreateError(lastScript.EndLineInfo, /*[XT_041]*/Res.Xslt_ScriptCompileException, e.Message));
323             }
324
325             if (!settings.CheckOnly) {
326                 foreach (string fileName in results.TempFiles) {
327                     allTempFiles.AddFile(fileName, allTempFiles.KeepFiles);
328                 }
329             }
330
331             foreach (CompilerError error in results.Errors) {
332                 FixErrorPosition(error, scriptsForLang);
333                 compiler.AddModule(error.FileName);
334             }
335
336             allErrors.AddRange(results.Errors);
337             return results.Errors.HasErrors ? null : results.CompiledAssembly;
338         }
339
340         private int assemblyCounter = 0;
341
342         private string GetLanguageName(CompilerInfo compilerInfo) {
343             Regex alphaNumeric = new Regex("^[0-9a-zA-Z]+$"); 
344             foreach (string name in compilerInfo.GetLanguages()) {
345                 if (alphaNumeric.IsMatch(name))
346                     return name;
347             }
348             return "script" + (++assemblyCounter).ToString(CultureInfo.InvariantCulture);
349         }
350
351         // The position of a compile error may be outside of all user code snippets (for example, in case of
352         // unclosed '{'). In that case filename would be the name of the temporary file, and not the name
353         // of the stylesheet file. Exposing the path of the temporary file is considered to be a security issue,
354         // so here we check that filename is amongst user files.
355         private static void FixErrorPosition(CompilerError error, List<ScriptClass> scriptsForLang) {
356             string fileName = error.FileName;
357             string uri;
358
359             foreach (ScriptClass script in scriptsForLang) {
360                 // We assume that CodeDom provider returns absolute paths (VSWhidbey 289665).
361                 // Note that casing may be different.
362                 if (script.scriptUris.TryGetValue(fileName, out uri)) {
363                     // The error position is within one of user stylesheets, its URI may be reported
364                     error.FileName = uri;
365                     return;
366                 }
367             }
368
369             // Error is outside user code snippeets, we should hide filename for security reasons.
370             // Return filename and position of the end of the last script block for the given class.
371             int idx, scriptNumber;
372             ScriptClass errScript = scriptsForLang[scriptsForLang.Count - 1];
373
374             // Normally temporary source files are named according to the scheme "<random name>.<script number>.
375             // <language extension>". Try to extract the middle part to find the relevant script class. In case
376             // of a non-standard CodeDomProvider, use the last script class.
377             fileName = Path.GetFileNameWithoutExtension(fileName);
378             if ((idx = fileName.LastIndexOf('.')) >= 0)
379                 if (int.TryParse(fileName.Substring(idx + 1), NumberStyles.None, NumberFormatInfo.InvariantInfo, out scriptNumber))
380                     if ((uint)scriptNumber < scriptsForLang.Count) {
381                         errScript = scriptsForLang[scriptNumber];
382                     }
383
384             error.FileName  = errScript.endUri;
385             error.Line      = errScript.endLoc.Line;
386             error.Column    = errScript.endLoc.Pos;
387         }
388     }
389 }