1 //------------------------------------------------------------------------------
2 // <copyright file="Scripts.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <spec>http://devdiv/Documents/Whidbey/CLR/CurrentSpecs/BCL/CodeDom%20Activation.doc</spec>
7 //------------------------------------------------------------------------------
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;
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;
26 namespace System.Xml.Xsl.Xslt {
27 using Res = System.Xml.Utils.Res;
29 internal class ScriptClass {
31 public CompilerInfo compilerInfo;
32 public StringCollection refAssemblies;
33 public StringCollection nsImports;
34 public CodeTypeDeclaration typeDecl;
35 public bool refAssembliesByHref;
37 public Dictionary<string, string> scriptUris;
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
42 public Location endLoc;
44 public ScriptClass(string ns, CompilerInfo compilerInfo) {
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
58 private static long scriptClassCounter = 0;
60 private static string GenerateUniqueClassName() {
61 return "Script" + Interlocked.Increment(ref scriptClassCounter);
64 public void AddScriptBlock(string source, string uriString, int lineNumber, Location end) {
65 CodeSnippetTypeMember scriptSnippet = new CodeSnippetTypeMember(source);
66 string fileName = SourceLineInfo.GetFileName(uriString);
68 scriptSnippet.LinePragma = new CodeLinePragma(fileName, lineNumber);
69 scriptUris[fileName] = uriString;
71 typeDecl.Members.Add(scriptSnippet);
73 this.endUri = uriString;
77 public ISourceLineInfo EndLineInfo {
79 return new SourceLineInfo(this.endUri, this.endLoc, this.endLoc);
84 internal class Scripts {
85 private const string ScriptClassesNamespace = "System.Xml.Xsl.CompiledQuery";
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();
92 public Scripts(Compiler compiler) {
93 this.compiler = compiler;
96 public Dictionary<string, Type> ScriptClasses {
97 get { return nsToType; }
100 public XmlExtensionFunction ResolveFunction(string name, string ns, int numArgs, IErrorHelper errorHelper) {
102 if (nsToType.TryGetValue(ns, out type)) {
104 return extFuncs.Bind(name, ns, numArgs, type, XmlQueryRuntime.EarlyBoundFlags);
106 catch (XslTransformException e) {
107 errorHelper.ReportError(e.Message);
113 public ScriptClass GetScriptClass(string ns, string language, IErrorHelper errorHelper) {
114 #if CONFIGURATION_DEP
115 CompilerInfo compilerInfo;
117 compilerInfo = CodeDomProvider.GetCompilerInfo(language);
118 Debug.Assert(compilerInfo != null);
120 catch (ConfigurationException) {
121 // There is no CodeDom provider defined for this language
122 errorHelper.ReportError(/*[XT_010]*/Res.Xslt_ScriptInvalidLanguage, language);
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);
137 ScriptClass newScriptClass = new ScriptClass(ns, compilerInfo);
138 newScriptClass.typeDecl.TypeAttributes = TypeAttributes.Public;
139 scriptClasses.Add(newScriptClass);
140 return newScriptClass;
146 //------------------------------------------------
148 //------------------------------------------------
150 public void CompileScripts() {
151 List<ScriptClass> scriptsForLang = new List<ScriptClass>();
153 for (int i = 0; i < scriptClasses.Count; i++) {
154 // If the script is already compiled, skip it
155 if (scriptClasses[i] == null)
158 // Group together scripts with the same CompilerInfo
159 CompilerInfo compilerInfo = scriptClasses[i].compilerInfo;
160 scriptsForLang.Clear();
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;
170 Assembly assembly = CompileAssembly(scriptsForLang);
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);
183 // Namespaces we always import when compiling
184 private static readonly string[] defaultNamespaces = new string[] {
186 "System.Collections",
188 "System.Text.RegularExpressions",
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;
207 provider = lastScript.compilerInfo.CreateProvider();
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));
215 #if !FEATURE_PAL // visualbasic
216 isVB = provider is Microsoft.VisualBasic.VBCodeProvider;
217 #endif // !FEATURE_PAL
219 CodeCompileUnit[] codeUnits = new CodeCompileUnit[scriptsForLang.Count];
220 CompilerParameters compilParams = lastScript.compilerInfo.CreateDefaultCompilerParameters();
225 compilParams.ReferencedAssemblies.Add(typeof(System.Xml.Res).Assembly.Location);
226 compilParams.ReferencedAssemblies.Add("System.dll");
228 compilParams.ReferencedAssemblies.Add("Microsoft.VisualBasic.dll");
231 bool refAssembliesByHref = false;
233 for (int idx = 0; idx < scriptsForLang.Count; idx++) {
234 ScriptClass script = scriptsForLang[idx];
235 CodeNamespace scriptNs = new CodeNamespace(ScriptClassesNamespace);
237 // Add imported namespaces
238 foreach (string ns in defaultNamespaces) {
239 scriptNs.Imports.Add(new CodeNamespaceImport(ns));
242 scriptNs.Imports.Add(new CodeNamespaceImport("Microsoft.VisualBasic"));
244 foreach (string ns in script.nsImports) {
245 scriptNs.Imports.Add(new CodeNamespaceImport(ns));
248 scriptNs.Types.Add(script.typeDecl);
250 CodeCompileUnit unit = new CodeCompileUnit(); {
251 unit.Namespaces.Add(scriptNs);
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
260 // Put SecurityTransparentAttribute and SecurityRulesAttribute on the first CodeCompileUnit only
262 unit.AssemblyCustomAttributes.Add(new CodeAttributeDeclaration("System.Security.SecurityTransparentAttribute"));
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"))));
274 codeUnits[idx] = unit;
275 foreach (string name in script.refAssemblies) {
276 compilParams.ReferencedAssemblies.Add(name);
279 refAssembliesByHref |= script.refAssembliesByHref;
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;
287 string asmPath = compiler.ScriptAssemblyPath;
288 if (asmPath != null && scriptsForLang.Count < scriptClasses.Count) {
289 asmPath = Path.ChangeExtension(asmPath, "." + GetLanguageName(lastScript.compilerInfo) + Path.GetExtension(asmPath));
291 compilParams.OutputAssembly = asmPath;
293 string tempDir = (settings.TempFiles != null) ? settings.TempFiles.TempDir : null;
294 compilParams.TempFiles = new TempFileCollection(tempDir);
296 // We need only .dll and .pdb, but there is no way to specify that
297 bool keepFiles = (compiler.IsDebug && asmPath == null);
299 keepFiles = keepFiles || XmlILTrace.IsEnabled;
301 keepFiles = keepFiles && !settings.CheckOnly;
304 compilParams.TempFiles.KeepFiles = keepFiles;
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;
314 CompilerResults results;
317 results = provider.CompileAssemblyFromDom(compilParams, codeUnits);
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));
325 if (!settings.CheckOnly) {
326 foreach (string fileName in results.TempFiles) {
327 allTempFiles.AddFile(fileName, allTempFiles.KeepFiles);
331 foreach (CompilerError error in results.Errors) {
332 FixErrorPosition(error, scriptsForLang);
333 compiler.AddModule(error.FileName);
336 allErrors.AddRange(results.Errors);
337 return results.Errors.HasErrors ? null : results.CompiledAssembly;
340 private int assemblyCounter = 0;
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))
348 return "script" + (++assemblyCounter).ToString(CultureInfo.InvariantCulture);
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;
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;
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];
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];
384 error.FileName = errScript.endUri;
385 error.Line = errScript.endLoc.Line;
386 error.Column = errScript.endLoc.Pos;