536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
390 lines
17 KiB
C#
390 lines
17 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="Scripts.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">Microsoft</owner>
|
|
// <spec>http://devdiv/Documents/Whidbey/CLR/CurrentSpecs/BCL/CodeDom%20Activation.doc</spec>
|
|
//------------------------------------------------------------------------------
|
|
|
|
using System.CodeDom;
|
|
using System.CodeDom.Compiler;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Configuration;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.RegularExpressions;
|
|
using System.Security.Permissions;
|
|
using System.Threading;
|
|
using System.Xml.Xsl.IlGen;
|
|
using System.Xml.Xsl.Runtime;
|
|
using System.Runtime.Versioning;
|
|
|
|
namespace System.Xml.Xsl.Xslt {
|
|
using Res = System.Xml.Utils.Res;
|
|
|
|
internal class ScriptClass {
|
|
public string ns;
|
|
public CompilerInfo compilerInfo;
|
|
public StringCollection refAssemblies;
|
|
public StringCollection nsImports;
|
|
public CodeTypeDeclaration typeDecl;
|
|
public bool refAssembliesByHref;
|
|
|
|
public Dictionary<string, string> scriptUris;
|
|
|
|
// These two fields are used to report a compile error when its position is outside
|
|
// of all user code snippets in the generated temporary file
|
|
public string endUri;
|
|
public Location endLoc;
|
|
|
|
public ScriptClass(string ns, CompilerInfo compilerInfo) {
|
|
this.ns = ns;
|
|
this.compilerInfo = compilerInfo;
|
|
this.refAssemblies = new StringCollection();
|
|
this.nsImports = new StringCollection();
|
|
this.typeDecl = new CodeTypeDeclaration(GenerateUniqueClassName());
|
|
this.refAssembliesByHref = false;
|
|
this.scriptUris = new Dictionary<string, string>(
|
|
#if !FEATURE_CASE_SENSITIVE_FILESYSTEM
|
|
StringComparer.OrdinalIgnoreCase
|
|
#endif
|
|
);
|
|
}
|
|
|
|
private static long scriptClassCounter = 0;
|
|
|
|
private static string GenerateUniqueClassName() {
|
|
return "Script" + Interlocked.Increment(ref scriptClassCounter);
|
|
}
|
|
|
|
public void AddScriptBlock(string source, string uriString, int lineNumber, Location end) {
|
|
CodeSnippetTypeMember scriptSnippet = new CodeSnippetTypeMember(source);
|
|
string fileName = SourceLineInfo.GetFileName(uriString);
|
|
if (lineNumber > 0) {
|
|
scriptSnippet.LinePragma = new CodeLinePragma(fileName, lineNumber);
|
|
scriptUris[fileName] = uriString;
|
|
}
|
|
typeDecl.Members.Add(scriptSnippet);
|
|
|
|
this.endUri = uriString;
|
|
this.endLoc = end;
|
|
}
|
|
|
|
public ISourceLineInfo EndLineInfo {
|
|
get {
|
|
return new SourceLineInfo(this.endUri, this.endLoc, this.endLoc);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class Scripts {
|
|
private const string ScriptClassesNamespace = "System.Xml.Xsl.CompiledQuery";
|
|
|
|
private Compiler compiler;
|
|
private List<ScriptClass> scriptClasses = new List<ScriptClass>();
|
|
private Dictionary<string, Type> nsToType = new Dictionary<string, Type>();
|
|
private XmlExtensionFunctionTable extFuncs = new XmlExtensionFunctionTable();
|
|
|
|
public Scripts(Compiler compiler) {
|
|
this.compiler = compiler;
|
|
}
|
|
|
|
public Dictionary<string, Type> ScriptClasses {
|
|
get { return nsToType; }
|
|
}
|
|
|
|
public XmlExtensionFunction ResolveFunction(string name, string ns, int numArgs, IErrorHelper errorHelper) {
|
|
Type type;
|
|
if (nsToType.TryGetValue(ns, out type)) {
|
|
try {
|
|
return extFuncs.Bind(name, ns, numArgs, type, XmlQueryRuntime.EarlyBoundFlags);
|
|
}
|
|
catch (XslTransformException e) {
|
|
errorHelper.ReportError(e.Message);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public ScriptClass GetScriptClass(string ns, string language, IErrorHelper errorHelper) {
|
|
#if CONFIGURATION_DEP
|
|
CompilerInfo compilerInfo;
|
|
try {
|
|
compilerInfo = CodeDomProvider.GetCompilerInfo(language);
|
|
Debug.Assert(compilerInfo != null);
|
|
}
|
|
catch (ConfigurationException) {
|
|
// There is no CodeDom provider defined for this language
|
|
errorHelper.ReportError(/*[XT_010]*/Res.Xslt_ScriptInvalidLanguage, language);
|
|
return null;
|
|
}
|
|
|
|
foreach (ScriptClass scriptClass in scriptClasses) {
|
|
if (ns == scriptClass.ns) {
|
|
// Use object comparison because CompilerInfo.Equals may throw
|
|
if (compilerInfo != scriptClass.compilerInfo) {
|
|
errorHelper.ReportError(/*[XT_011]*/Res.Xslt_ScriptMixedLanguages, ns);
|
|
return null;
|
|
}
|
|
return scriptClass;
|
|
}
|
|
}
|
|
|
|
ScriptClass newScriptClass = new ScriptClass(ns, compilerInfo);
|
|
newScriptClass.typeDecl.TypeAttributes = TypeAttributes.Public;
|
|
scriptClasses.Add(newScriptClass);
|
|
return newScriptClass;
|
|
#else
|
|
return null;
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------
|
|
// Compilation
|
|
//------------------------------------------------
|
|
|
|
public void CompileScripts() {
|
|
List<ScriptClass> scriptsForLang = new List<ScriptClass>();
|
|
|
|
for (int i = 0; i < scriptClasses.Count; i++) {
|
|
// If the script is already compiled, skip it
|
|
if (scriptClasses[i] == null)
|
|
continue;
|
|
|
|
// Group together scripts with the same CompilerInfo
|
|
CompilerInfo compilerInfo = scriptClasses[i].compilerInfo;
|
|
scriptsForLang.Clear();
|
|
|
|
for (int j = i; j < scriptClasses.Count; j++) {
|
|
// Use object comparison because CompilerInfo.Equals may throw
|
|
if (scriptClasses[j] != null && scriptClasses[j].compilerInfo == compilerInfo) {
|
|
scriptsForLang.Add(scriptClasses[j]);
|
|
scriptClasses[j] = null;
|
|
}
|
|
}
|
|
|
|
Assembly assembly = CompileAssembly(scriptsForLang);
|
|
|
|
if (assembly != null) {
|
|
foreach (ScriptClass script in scriptsForLang) {
|
|
Type clrType = assembly.GetType(ScriptClassesNamespace + Type.Delimiter + script.typeDecl.Name);
|
|
if (clrType != null) {
|
|
nsToType.Add(script.ns, clrType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Namespaces we always import when compiling
|
|
private static readonly string[] defaultNamespaces = new string[] {
|
|
"System",
|
|
"System.Collections",
|
|
"System.Text",
|
|
"System.Text.RegularExpressions",
|
|
"System.Xml",
|
|
"System.Xml.Xsl",
|
|
"System.Xml.XPath",
|
|
};
|
|
|
|
// SxS: This method does not take any resource name and does not expose any resources to the caller.
|
|
// It's OK to suppress the SxS warning.
|
|
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
[ResourceExposure(ResourceScope.None)]
|
|
private Assembly CompileAssembly(List<ScriptClass> scriptsForLang) {
|
|
TempFileCollection allTempFiles = compiler.CompilerResults.TempFiles;
|
|
CompilerErrorCollection allErrors = compiler.CompilerResults.Errors;
|
|
ScriptClass lastScript = scriptsForLang[scriptsForLang.Count - 1];
|
|
CodeDomProvider provider;
|
|
bool isVB = false;
|
|
|
|
try {
|
|
provider = lastScript.compilerInfo.CreateProvider();
|
|
}
|
|
catch (ConfigurationException e) {
|
|
// The CodeDom provider type could not be located, or some error in machine.config
|
|
allErrors.Add(compiler.CreateError(lastScript.EndLineInfo, /*[XT_041]*/Res.Xslt_ScriptCompileException, e.Message));
|
|
return null;
|
|
}
|
|
|
|
#if !FEATURE_PAL // visualbasic
|
|
isVB = provider is Microsoft.VisualBasic.VBCodeProvider;
|
|
#endif // !FEATURE_PAL
|
|
|
|
CodeCompileUnit[] codeUnits = new CodeCompileUnit[scriptsForLang.Count];
|
|
CompilerParameters compilParams = lastScript.compilerInfo.CreateDefaultCompilerParameters();
|
|
|
|
//
|
|
|
|
|
|
compilParams.ReferencedAssemblies.Add(typeof(System.Xml.Res).Assembly.Location);
|
|
compilParams.ReferencedAssemblies.Add("System.dll");
|
|
if (isVB) {
|
|
compilParams.ReferencedAssemblies.Add("Microsoft.VisualBasic.dll");
|
|
}
|
|
|
|
bool refAssembliesByHref = false;
|
|
|
|
for (int idx = 0; idx < scriptsForLang.Count; idx++) {
|
|
ScriptClass script = scriptsForLang[idx];
|
|
CodeNamespace scriptNs = new CodeNamespace(ScriptClassesNamespace);
|
|
|
|
// Add imported namespaces
|
|
foreach (string ns in defaultNamespaces) {
|
|
scriptNs.Imports.Add(new CodeNamespaceImport(ns));
|
|
}
|
|
if (isVB) {
|
|
scriptNs.Imports.Add(new CodeNamespaceImport("Microsoft.VisualBasic"));
|
|
}
|
|
foreach (string ns in script.nsImports) {
|
|
scriptNs.Imports.Add(new CodeNamespaceImport(ns));
|
|
}
|
|
|
|
scriptNs.Types.Add(script.typeDecl);
|
|
|
|
CodeCompileUnit unit = new CodeCompileUnit(); {
|
|
unit.Namespaces.Add(scriptNs);
|
|
|
|
if (isVB) {
|
|
// This settings have sense for Visual Basic only. In future releases we may allow to specify
|
|
// them explicitly in the msxsl:script element.
|
|
unit.UserData["AllowLateBound"] = true; // Allow variables to be declared untyped
|
|
unit.UserData["RequireVariableDeclaration"] = false; // Allow variables to be undeclared
|
|
}
|
|
|
|
// Put SecurityTransparentAttribute and SecurityRulesAttribute on the first CodeCompileUnit only
|
|
if (idx == 0) {
|
|
unit.AssemblyCustomAttributes.Add(new CodeAttributeDeclaration("System.Security.SecurityTransparentAttribute"));
|
|
|
|
// We want the assemblies generated for scripts to stick to the old security model
|
|
unit.AssemblyCustomAttributes.Add(
|
|
new CodeAttributeDeclaration(
|
|
new CodeTypeReference(typeof(System.Security.SecurityRulesAttribute)),
|
|
new CodeAttributeArgument(
|
|
new CodeFieldReferenceExpression(
|
|
new CodeTypeReferenceExpression(typeof(System.Security.SecurityRuleSet)), "Level1"))));
|
|
}
|
|
}
|
|
|
|
codeUnits[idx] = unit;
|
|
foreach (string name in script.refAssemblies) {
|
|
compilParams.ReferencedAssemblies.Add(name);
|
|
}
|
|
|
|
refAssembliesByHref |= script.refAssembliesByHref;
|
|
}
|
|
|
|
XsltSettings settings = compiler.Settings;
|
|
compilParams.WarningLevel = settings.WarningLevel >= 0 ? settings.WarningLevel : compilParams.WarningLevel;
|
|
compilParams.TreatWarningsAsErrors = settings.TreatWarningsAsErrors;
|
|
compilParams.IncludeDebugInformation = compiler.IsDebug;
|
|
|
|
string asmPath = compiler.ScriptAssemblyPath;
|
|
if (asmPath != null && scriptsForLang.Count < scriptClasses.Count) {
|
|
asmPath = Path.ChangeExtension(asmPath, "." + GetLanguageName(lastScript.compilerInfo) + Path.GetExtension(asmPath));
|
|
}
|
|
compilParams.OutputAssembly = asmPath;
|
|
|
|
string tempDir = (settings.TempFiles != null) ? settings.TempFiles.TempDir : null;
|
|
compilParams.TempFiles = new TempFileCollection(tempDir);
|
|
|
|
// We need only .dll and .pdb, but there is no way to specify that
|
|
bool keepFiles = (compiler.IsDebug && asmPath == null);
|
|
#if DEBUG
|
|
keepFiles = keepFiles || XmlILTrace.IsEnabled;
|
|
#endif
|
|
keepFiles = keepFiles && !settings.CheckOnly;
|
|
|
|
|
|
compilParams.TempFiles.KeepFiles = keepFiles;
|
|
|
|
// If GenerateInMemory == true, then CodeDom loads the compiled assembly using Assembly.Load(byte[])
|
|
// instead of Assembly.Load(AssemblyName). That means the assembly will be loaded in the anonymous
|
|
// context (http://blogs.msdn.com/Microsoft/archive/2003/05/29/57143.aspx), and its dependencies can only
|
|
// be loaded from the Load context or using AssemblyResolve event. However we want to use the LoadFrom
|
|
// context to preload all dependencies specified by <ms:assembly href="uri-reference"/>, so we turn off
|
|
// GenerateInMemory here.
|
|
compilParams.GenerateInMemory = (asmPath == null && !compiler.IsDebug && !refAssembliesByHref) || settings.CheckOnly;
|
|
|
|
CompilerResults results;
|
|
|
|
try {
|
|
results = provider.CompileAssemblyFromDom(compilParams, codeUnits);
|
|
}
|
|
catch (ExternalException e) {
|
|
// Compiler might have created temporary files
|
|
results = new CompilerResults(compilParams.TempFiles);
|
|
results.Errors.Add(compiler.CreateError(lastScript.EndLineInfo, /*[XT_041]*/Res.Xslt_ScriptCompileException, e.Message));
|
|
}
|
|
|
|
if (!settings.CheckOnly) {
|
|
foreach (string fileName in results.TempFiles) {
|
|
allTempFiles.AddFile(fileName, allTempFiles.KeepFiles);
|
|
}
|
|
}
|
|
|
|
foreach (CompilerError error in results.Errors) {
|
|
FixErrorPosition(error, scriptsForLang);
|
|
compiler.AddModule(error.FileName);
|
|
}
|
|
|
|
allErrors.AddRange(results.Errors);
|
|
return results.Errors.HasErrors ? null : results.CompiledAssembly;
|
|
}
|
|
|
|
private int assemblyCounter = 0;
|
|
|
|
private string GetLanguageName(CompilerInfo compilerInfo) {
|
|
Regex alphaNumeric = new Regex("^[0-9a-zA-Z]+$");
|
|
foreach (string name in compilerInfo.GetLanguages()) {
|
|
if (alphaNumeric.IsMatch(name))
|
|
return name;
|
|
}
|
|
return "script" + (++assemblyCounter).ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
// The position of a compile error may be outside of all user code snippets (for example, in case of
|
|
// unclosed '{'). In that case filename would be the name of the temporary file, and not the name
|
|
// of the stylesheet file. Exposing the path of the temporary file is considered to be a security issue,
|
|
// so here we check that filename is amongst user files.
|
|
private static void FixErrorPosition(CompilerError error, List<ScriptClass> scriptsForLang) {
|
|
string fileName = error.FileName;
|
|
string uri;
|
|
|
|
foreach (ScriptClass script in scriptsForLang) {
|
|
// We assume that CodeDom provider returns absolute paths (VSWhidbey 289665).
|
|
// Note that casing may be different.
|
|
if (script.scriptUris.TryGetValue(fileName, out uri)) {
|
|
// The error position is within one of user stylesheets, its URI may be reported
|
|
error.FileName = uri;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Error is outside user code snippeets, we should hide filename for security reasons.
|
|
// Return filename and position of the end of the last script block for the given class.
|
|
int idx, scriptNumber;
|
|
ScriptClass errScript = scriptsForLang[scriptsForLang.Count - 1];
|
|
|
|
// Normally temporary source files are named according to the scheme "<random name>.<script number>.
|
|
// <language extension>". Try to extract the middle part to find the relevant script class. In case
|
|
// of a non-standard CodeDomProvider, use the last script class.
|
|
fileName = Path.GetFileNameWithoutExtension(fileName);
|
|
if ((idx = fileName.LastIndexOf('.')) >= 0)
|
|
if (int.TryParse(fileName.Substring(idx + 1), NumberStyles.None, NumberFormatInfo.InvariantInfo, out scriptNumber))
|
|
if ((uint)scriptNumber < scriptsForLang.Count) {
|
|
errScript = scriptsForLang[scriptNumber];
|
|
}
|
|
|
|
error.FileName = errScript.endUri;
|
|
error.Line = errScript.endLoc.Line;
|
|
error.Column = errScript.endLoc.Pos;
|
|
}
|
|
}
|
|
}
|