|
//------------------------------------------------------------------------------
// <copyright file="AssemblyBuilder.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.Compilation {
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Web.Management;
using System.Web.Security.Cryptography;
using System.Web.UI;
using System.Web.Util;
using System.Xml;
using System.Xml.Schema;
/*
* This class is used to handle a single compilation using a CodeDom compiler.
* It is instantiated via CompilerType.CreateAssemblyBuilder.
*/
public class AssemblyBuilder {
private CompilationSection _compConfig;
// CodeChecksumPragma.ChecksumAlgorithmId takes this GUID to represent a SHA1 hash of the file contents
// See: http://msdn.microsoft.com/en-us/library/system.codedom.codechecksumpragma.checksumalgorithmid.aspx
private static readonly Guid s_codeChecksumSha1Id = new Guid(0xff1816ec, 0xaa5e, 0x4d10, 0x87, 0xf7, 0x6f, 0x49, 0x63, 0x83, 0x34, 0x60);
private static readonly Guid s_codeChecksumSha256Id = new Guid(0x8829d00f, 0x11b8, 0x4213, 0x87, 0x8b, 0x77, 0x0e, 0x85, 0x97, 0xac, 0x16);
// List of BuildProviders involved in this compilation
// The key is either a virtual path, or the BuildProvider itself if
// it doesn't give us a virtual path.
// The value is the BuildProvider.
private Hashtable _buildProviders = new Hashtable(StringComparer.OrdinalIgnoreCase);
internal ICollection BuildProviders {
get { return _buildProviders.Values; }
}
// List of physical source files to be compiled
private StringSet _sourceFiles = new StringSet();
// CodeCompileUnit to hold various top level things we need to generate
private CodeCompileUnit _miscCodeCompileUnit;
// List of physical embedded resource files to be compiled
private StringSet _embeddedResourceFiles;
// The set of assemblies that we will be linked with
private AssemblySet _initialReferencedAssemblies;
// The additional set of assemblies that we will be linked with, and that are
// requested by various BuildProviders. We need to keep them separate to avoid
// having BuildProviders see assemblies that were requested by earlier providers,
// which would lead to unpredictable behavior (since the order is arbitrary)
private AssemblySet _additionalReferencedAssemblies;
internal CodeDomProvider _codeProvider;
private Hashtable _buildProviderToSourceFileMap;
// The type of CodeDom compiler (i.e. language, flags)
private CompilerType _compilerType;
internal Type CodeDomProviderType {
get { return _compilerType.CodeDomProviderType; }
}
// Used to generate fast Type factories
private ObjectFactoryCodeDomTreeGenerator _objectFactoryGenerator;
private StringResourceBuilder _stringResourceBuilder;
internal StringResourceBuilder StringResourceBuilder {
get {
if (_stringResourceBuilder == null)
_stringResourceBuilder = new StringResourceBuilder();
return _stringResourceBuilder;
}
}
// Used to create temporary source files
private TempFileCollection _tempFiles = new TempFileCollection(HttpRuntime.CodegenDirInternal);
private int _fileCount;
private string _cultureName;
internal string CultureName {
get { return _cultureName; }
set { _cultureName = value; }
}
private string _outputAssemblyName;
private string OutputAssemblyName {
get {
if (_outputAssemblyName == null) {
// If we don't have the assembly name, we should never have a culture
Debug.Assert(CultureName == null);
// If the assembly name was not specified, use a generated one based on the TempFileCollection.
// But prefix it with a fixed token, to make it easier to recognize the assembly (DevDiv 36625)
string basePath = _tempFiles.BasePath;
string baseFileName = Path.GetFileName(basePath);
_outputAssemblyName = BuildManager.WebAssemblyNamePrefix + baseFileName;
}
return _outputAssemblyName;
}
}
private int _maxBatchSize;
private long _maxBatchGeneratedFileSize;
private long _totalFileLength;
private CaseInsensitiveStringSet _registeredTypeNames;
internal bool ContainsTypeNames(ICollection typeNames) {
if (_registeredTypeNames != null && typeNames != null) {
foreach (String typeName in typeNames) {
if (_registeredTypeNames.Contains(typeName)) {
return true;
}
}
}
return false;
}
internal void AddTypeNames(ICollection typeNames) {
if (typeNames == null) {
return;
}
if (_registeredTypeNames == null) {
_registeredTypeNames = new CaseInsensitiveStringSet();
}
_registeredTypeNames.AddCollection(typeNames);
}
internal AssemblyBuilder(CompilationSection compConfig,
ICollection referencedAssemblies, CompilerType compilerType, string outputAssemblyName) {
_compConfig = compConfig;
_outputAssemblyName = outputAssemblyName;
// Clone the referenced assemblies
_initialReferencedAssemblies = AssemblySet.Create(referencedAssemblies);
// We need to clone it to avoid modifying the original (VSWhidbey 338935)
_compilerType = compilerType.Clone();
if (BuildManager.PrecompilingWithDebugInfo) {
// If the precompile flag indicates force debug, always compile as debug
_compilerType.CompilerParameters.IncludeDebugInformation = true;
}
else if (BuildManager.PrecompilingForDeployment) {
// If we're precompiling the app, never compile in debug mode (VSWhidbey 178377)
_compilerType.CompilerParameters.IncludeDebugInformation = false;
}
else if (DeploymentSection.RetailInternal) {
// If we're in retail deployment mode, always turn off debug (DevDiv 36396)
_compilerType.CompilerParameters.IncludeDebugInformation = false;
}
else if (_compConfig.AssemblyPostProcessorTypeInternal != null) {
// If an IAssemblyPostProcessor is registered always compile as debug
_compilerType.CompilerParameters.IncludeDebugInformation = true;
}
//
_tempFiles.KeepFiles = _compilerType.CompilerParameters.IncludeDebugInformation;
_codeProvider = CompilationUtil.CreateCodeDomProviderNonPublic(
_compilerType.CodeDomProviderType);
_maxBatchSize = _compConfig.MaxBatchSize;
_maxBatchGeneratedFileSize = _compConfig.MaxBatchGeneratedFileSize * 1024;
}
// Beginning of public contract
/// <devdoc>
/// Adds an assembly that will be referenced during compilation.
/// </devdoc>
public void AddAssemblyReference(Assembly a) {
if (_additionalReferencedAssemblies == null)
_additionalReferencedAssemblies = new AssemblySet();
_additionalReferencedAssemblies.Add(a);
}
/// <devdoc>
/// Adds an assembly that will be referenced during compilation. Also adds the
/// assembly the the ReferencedAssemblies list in the CodeCompileUnit.
/// </devdoc>
internal void AddAssemblyReference(Assembly a, CodeCompileUnit ccu) {
AddAssemblyReference(a);
Util.AddAssemblyToStringCollection(a, ccu.ReferencedAssemblies);
}
/// <devdoc>
/// Creates a new source file that will be added to the compilation. See the public overload
/// method for detail.
/// </devdoc>
internal virtual TextWriter CreateCodeFile(BuildProvider buildProvider, out string filename) {
string generatedFilePath = GetTempFilePhysicalPathWithAssert(_codeProvider.FileExtension);
filename = generatedFilePath;
if (buildProvider != null) {
if (_buildProviderToSourceFileMap == null)
_buildProviderToSourceFileMap = new Hashtable();
_buildProviderToSourceFileMap[buildProvider] = generatedFilePath;
buildProvider.SetContributedCode();
}
_sourceFiles.Add(generatedFilePath);
return CreateCodeFileWithAssert(generatedFilePath);
}
[FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
private StreamWriter CreateCodeFileWithAssert(string generatedFilePath) {
Stream temp = new FileStream(generatedFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
return new StreamWriter(temp, Encoding.UTF8);
}
/// <devdoc>
/// Creates a new source file that will be added to the compilation. The build provider
/// can write source code to this file using the returned TextWriter.
/// The build provider should close the TextWriter when it is done writing to it.
/// The build provider should pass itself as a parameter to this method.
/// </devdoc>
public TextWriter CreateCodeFile(BuildProvider buildProvider) {
// Ignore the unused filename param.
string filename;
return CreateCodeFile(buildProvider, out filename);
}
// Indicates whether the assemblyBuilder has reached its capacity limit.
internal bool IsBatchFull {
get {
return (_sourceFiles.Count >= _maxBatchSize) ||
(_totalFileLength >= _maxBatchGeneratedFileSize);
}
}
/// <devdoc>
/// Adds a CodeCompileUnit to the compilation. This is typically used as an
/// alternative to CreateSourceFile, by providers who are CodeDOM aware.
/// The build provider should pass itself as a parameter to this method.
/// </devdoc>
public void AddCodeCompileUnit(BuildProvider buildProvider, CodeCompileUnit compileUnit) {
// Add a checksum pragma to the compile unit if appropriate
AddChecksumPragma(buildProvider, compileUnit);
// Add all the referenced assemblies to the CodeCompileUnit in case the CodeDom
// provider needs them for code generation
Util.AddAssembliesToStringCollection(_initialReferencedAssemblies, compileUnit.ReferencedAssemblies);
// Merge the _additionalReferencedAssemblies from individul build providers
Util.AddAssembliesToStringCollection(_additionalReferencedAssemblies, compileUnit.ReferencedAssemblies);
String filename;
// Revert impersonation when generating source code in the codegen dir (VSWhidbey 176576)
using (new ProcessImpersonationContext()) {
TextWriter writer = CreateCodeFile(buildProvider, out filename);
try {
_codeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, null /*CodeGeneratorOptions*/);
}
finally {
writer.Flush();
writer.Close();
}
}
if (filename != null) {
_totalFileLength += GetFileLengthWithAssert(filename);
}
}
// Assert to be able to get the length of the file in the CodeGen dir
[FileIOPermission(SecurityAction.Assert, AllFiles = FileIOPermissionAccess.Read)]
private long GetFileLengthWithAssert(string filename) {
FileInfo info = new FileInfo(filename);
return info.Length;
}
/// <devdoc>
/// Tell the host about a type that is being generated. This allows the host
/// To generate a fast object factory for it.
/// </devdoc>
public void GenerateTypeFactory(string typeName) {
// Create the object factory generator on demand
if (_objectFactoryGenerator == null) {
_objectFactoryGenerator = new ObjectFactoryCodeDomTreeGenerator(OutputAssemblyName);
}
// Add a method to fast create this type
_objectFactoryGenerator.AddFactoryMethod(typeName);
}
internal void GenerateTypeFactory(string typeName, CodeCompileUnit ccu) {
// Create the object factory generator on demand
if (_objectFactoryGenerator == null) {
_objectFactoryGenerator = new ObjectFactoryCodeDomTreeGenerator(OutputAssemblyName);
}
// Add a method to fast create this type
_objectFactoryGenerator.AddFactoryMethod(typeName, ccu);
}
/// <devdoc>
/// Creates a new resource that will be added to the compilation. The build provider
/// can write to it using the returned Stream.
/// The build provider should close the Stream when it is done writing to it.
/// The build provider should pass itself as a parameter to this method.
/// </devdoc>
public Stream CreateEmbeddedResource(BuildProvider buildProvider, string name) {
// Make sure it's just a valid simple file name
if (!Util.IsValidFileName(name)) {
throw new ArgumentException(null, name);
}
string resourceDir = BuildManager.CodegenResourceDir;
string resourceFile = Path.Combine(resourceDir, name);
CreateTempResourceDirectoryIfNecessary();
_tempFiles.AddFile(resourceFile, _tempFiles.KeepFiles);
if (_embeddedResourceFiles == null)
_embeddedResourceFiles = new StringSet();
_embeddedResourceFiles.Add(resourceFile);
// Assert to be able to create the file in the temp dir
InternalSecurityPermissions.FileWriteAccess(resourceDir).Assert();
return File.OpenWrite(resourceFile);
}
[FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
private void CreateTempResourceDirectoryIfNecessary() {
// Create the temp resource directory if needed
string resourceDir = BuildManager.CodegenResourceDir;
if (!FileUtil.DirectoryExists(resourceDir)) {
Directory.CreateDirectory(resourceDir);
}
}
/// <devdoc>
/// Returns a CodeDomProvider that the build provider can use to generate a CodeCompileUnit.
/// </devdoc>
public CodeDomProvider CodeDomProvider {
get { return _codeProvider; }
}
private string _tempFilePhysicalPathPrefix;
private string TempFilePhysicalPathPrefix {
get {
if (_tempFilePhysicalPathPrefix == null) {
_tempFilePhysicalPathPrefix = Path.Combine(_tempFiles.TempDir, OutputAssemblyName) + ".";
// Append the culture name to avoid naming conflicts
if (CultureName != null) {
_tempFilePhysicalPathPrefix += CultureName + "_";
}
}
return _tempFilePhysicalPathPrefix;
}
}
/// <devdoc>
/// Returns the physical path to a temporary file that the build provider
/// can use for intermediate results. Note that the file is not actually
/// created. It is up to the build provider to do this.
/// The temp file's extension is passed in by the build provider.
/// The file is automatically deleted after the compilation, so the
/// build provider does not need to explicitly delete it.
/// </devdoc>
public string GetTempFilePhysicalPath(string extension) {
// Do the right thing depending on whether the extension include the starting '.'
string tempPath;
if (!String.IsNullOrEmpty(extension) && extension[0] == '.') {
tempPath = TempFilePhysicalPathPrefix + ((_fileCount++) + extension);
}
else {
tempPath = TempFilePhysicalPathPrefix + ((_fileCount++) + "." + extension);
}
_tempFiles.AddFile(tempPath, _tempFiles.KeepFiles);
InternalSecurityPermissions.PathDiscovery(tempPath).Demand();
return tempPath;
}
// End of public contract
[FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
internal string GetTempFilePhysicalPathWithAssert(string extension) {
return GetTempFilePhysicalPath(extension);
}
private void AddCompileWithBuildProvider(VirtualPath virtualPath, BuildProvider owningBuildProvider) {
BuildProvider buildProvider = BuildManager.CreateBuildProvider(virtualPath,
_compConfig, _initialReferencedAssemblies, true /*failIfUnknown*/);
// Since it's referenced via compileWith, it doesn't need its own build result
buildProvider.SetNoBuildResult();
// If it's a CompileWith provider, remember the main provider
SourceFileBuildProvider sourceBuildProvider = buildProvider as SourceFileBuildProvider;
if (sourceBuildProvider != null)
sourceBuildProvider.OwningBuildProvider = owningBuildProvider;
AddBuildProvider(buildProvider);
}
internal virtual void AddBuildProvider(BuildProvider buildProvider) {
// By default, use the build provider itself as the key
object hashtableKey = buildProvider;
bool isFolderLevel = false;
// If the buildProvider is a folderLevel build provider, use the build provider itself
// so that multiple build providers can work on the same path.
if (_compConfig.FolderLevelBuildProviders != null) {
Type t = buildProvider.GetType();
isFolderLevel = _compConfig.FolderLevelBuildProviders.IsFolderLevelBuildProvider(t);
}
// Keep track of the build provider's virtual path, if any
if (buildProvider.VirtualPath != null && !isFolderLevel) {
// It has a virtual path, so use that as the key
hashtableKey = buildProvider.VirtualPath;
// If we already had it, ignore it. This can happen when there is a user control
// with a code beside in App_Code (VSWhidbey 481426)
if (_buildProviders.ContainsKey(hashtableKey))
return;
}
_buildProviders[hashtableKey] = buildProvider;
// Ask the provider to generate the code
// If it throws an Xml exception, extra the relevant info and turn it
// into our own ParseException
try {
buildProvider.GenerateCode(this);
}
catch (XmlException e) {
throw new HttpParseException(e.Message, null /*innerException*/,
buildProvider.VirtualPath, null /*sourceCode*/, e.LineNumber);
}
catch (XmlSchemaException e) {
throw new HttpParseException(e.Message, null /*innerException*/,
buildProvider.VirtualPath, null /*sourceCode*/, e.LineNumber);
}
catch (Exception e) {
throw new HttpParseException(e.Message, e,
buildProvider.VirtualPath, null /*sourceCode*/, 1);
}
// Handle any 'compileWith' dependencies, i.e. files that must be compiled
// within the same assembly as the current file
InternalBuildProvider internalBuildProvider = buildProvider as InternalBuildProvider;
if (internalBuildProvider != null) {
ICollection compileWith = internalBuildProvider.GetCompileWithDependencies();
if (compileWith != null) {
foreach (VirtualPath virtualPath in compileWith) {
// If we already have it, ignore it
if (_buildProviders.ContainsKey(virtualPath.VirtualPathString))
continue;
// Add the compileWith dependency to our compilation
AddCompileWithBuildProvider(virtualPath, internalBuildProvider);
}
}
}
}
private void AddAssemblyCultureAttribute() {
if (CultureName == null) return;
CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(
new CodeTypeReference(typeof(System.Reflection.AssemblyCultureAttribute)),
new CodeAttributeArgument[] {
new CodeAttributeArgument(new CodePrimitiveExpression(CultureName))});
AddAssemblyAttribute(declaration);
}
private void AddAspNetGeneratedCodeAttribute() {
CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(
new CodeTypeReference(typeof(GeneratedCodeAttribute)));
declaration.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression("ASP.NET")));
declaration.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(VersionInfo.SystemWebVersion)));
AddAssemblyAttribute(declaration);
}
private void AddAllowPartiallyTrustedCallersAttribute() {
if (BuildManager.CompileWithAllowPartiallyTrustedCallersAttribute) {
CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(
new CodeTypeReference(typeof(AllowPartiallyTrustedCallersAttribute)));
AddAssemblyAttribute(declaration);
}
}
private void AddAssemblyKeyFileAttribute() {
if (!String.IsNullOrEmpty(BuildManager.StrongNameKeyFile)) {
CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(
new CodeTypeReference(typeof(AssemblyKeyFileAttribute)),
new CodeAttributeArgument(new CodePrimitiveExpression(BuildManager.StrongNameKeyFile)));
AddAssemblyAttribute(declaration);
}
}
private void AddAssemblyKeyContainerAttribute() {
if (!String.IsNullOrEmpty(BuildManager.StrongNameKeyContainer)) {
CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(
new CodeTypeReference(typeof(AssemblyKeyNameAttribute)),
new CodeAttributeArgument(new CodePrimitiveExpression(BuildManager.StrongNameKeyContainer)));
AddAssemblyAttribute(declaration);
}
}
private void AddAssemblyDelaySignAttribute() {
if (BuildManager.CompileWithDelaySignAttribute) {
CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(
new CodeTypeReference(typeof(AssemblyDelaySignAttribute)),
new CodeAttributeArgument(new CodePrimitiveExpression(true)));
AddAssemblyAttribute(declaration);
}
}
private void AddSecurityRulesAttribute() {
// Skip applying the attribute if targeting 2.0/3.5, since the attribute
// is only available in 4.0 and above.
if (MultiTargetingUtil.IsTargetFramework20 || MultiTargetingUtil.IsTargetFramework35) {
return;
}
TrustSection trustSection = RuntimeConfig.GetAppConfig().Trust;
CodeAttributeDeclaration declaration;
Type attrType = typeof(SecurityRulesAttribute);
Type enumType = typeof(SecurityRuleSet);
if (trustSection.LegacyCasModel) {
SecurityRuleSet set = SecurityRuleSet.Level1;
string fieldName = Enum.GetName(enumType, set);
CodeFieldReferenceExpression field = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(enumType), fieldName);
declaration = new CodeAttributeDeclaration(new CodeTypeReference(attrType), new CodeAttributeArgument(field));
AddAssemblyAttribute(declaration);
}
else {
SecurityRuleSet set = SecurityRuleSet.Level2;
string fieldName = Enum.GetName(enumType, set);
CodeFieldReferenceExpression field = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(enumType), fieldName);
declaration = new CodeAttributeDeclaration(new CodeTypeReference(attrType), new CodeAttributeArgument(field));
AddAssemblyAttribute(declaration);
}
}
private void AddTargetFrameworkAttribute() {
if (MultiTargetingUtil.TargetFrameworkVersion.Major >= 4) {
CodeAttributeDeclaration declaration = new CodeAttributeDeclaration(
new CodeTypeReference(typeof(System.Runtime.Versioning.TargetFrameworkAttribute)),
new CodeAttributeArgument(new CodePrimitiveExpression(BuildManager.TargetFramework.FullName)));
AddAssemblyAttribute(declaration);
}
}
// Add an assembly level attribute to the assembly
private void AddAssemblyAttribute(CodeAttributeDeclaration declaration) {
if (_miscCodeCompileUnit == null)
_miscCodeCompileUnit = new CodeCompileUnit();
_miscCodeCompileUnit.AssemblyCustomAttributes.Add(declaration);
}
private void GenerateMiscCodeCompileUnit() {
// If there aren't any, return
if (_miscCodeCompileUnit == null)
return;
AddCodeCompileUnit(null /*buildProvider*/, _miscCodeCompileUnit);
}
// Add a checksum pragma. This is used for improved debugging experience.
private void AddChecksumPragma(BuildProvider buildProvider, CodeCompileUnit compileUnit) {
// If we can't get a virtual path, do nothing
if (buildProvider == null || buildProvider.VirtualPath == null)
return;
// Only do this if we're compiling in debug mode
if (!_compilerType.CompilerParameters.IncludeDebugInformation)
return;
string physicalPath = HostingEnvironment.MapPathInternal(buildProvider.VirtualPath);
// Only do this is the file physically exists, which it would not in the
// case of a non-file based VirtualPathProvider. In such case, there is
// no point in putting the pragma, since the debugger could not locate
// the file anyway.
if (!File.Exists(physicalPath))
return;
string hashAlgorithm = BinaryCompatibility.Current.TargetsAtLeastFramework472 ? "SHA256" : "SHA1";
CodeChecksumPragma pragma = new CodeChecksumPragma() {
ChecksumAlgorithmId = (BinaryCompatibility.Current.TargetsAtLeastFramework472 ? s_codeChecksumSha256Id : s_codeChecksumSha1Id)
};
if (_compConfig.UrlLinePragmas) {
pragma.FileName = ErrorFormatter.MakeHttpLinePragma(buildProvider.VirtualPathObject.VirtualPathString);
}
else {
pragma.FileName = physicalPath;
}
// Generate a SHA1 hash from the contents of the file
using (Stream stream = new FileStream(physicalPath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
using (HashAlgorithm ha = (HashAlgorithm)CryptoConfig.CreateFromName(hashAlgorithm)) {
pragma.ChecksumData = ha.ComputeHash(stream);
}
}
// Add the pragma to the CodeCompileUnit
compileUnit.StartDirectives.Add(pragma);
}
internal CompilerParameters GetCompilerParameters() {
CompilerParameters compilParams = _compilerType.CompilerParameters;
string dir = _tempFiles.TempDir;
// If a culture is set, modify the assembly name and location based on it
if (CultureName != null) {
dir = Path.Combine(dir, CultureName);
Directory.CreateDirectory(dir);
compilParams.OutputAssembly = Path.Combine(dir, OutputAssemblyName + ".resources.dll");
}
else {
compilParams.OutputAssembly = Path.Combine(dir, OutputAssemblyName + ".dll");
}
// If such file already exist, try to delete or rename it
if (File.Exists(compilParams.OutputAssembly))
Util.RemoveOrRenameFile(compilParams.OutputAssembly);
compilParams.TempFiles = _tempFiles;
// Create the string resource file (shared by all the pages we're compiling)
if (_stringResourceBuilder != null && _stringResourceBuilder.HasStrings) {
string resFileName = _tempFiles.AddExtension("res");
_stringResourceBuilder.CreateResourceFile(resFileName);
compilParams.Win32Resource = resFileName;
}
// Add all the embedded resources to the compilParams
if (_embeddedResourceFiles != null) {
foreach (string aname in _embeddedResourceFiles)
compilParams.EmbeddedResources.Add(aname);
}
// Merge the two sets of assemblies
if (_additionalReferencedAssemblies != null) {
foreach (Assembly assembly in _additionalReferencedAssemblies) {
_initialReferencedAssemblies.Add(assembly);
}
}
// Add all the referenced assemblies to the compilParams
Util.AddAssembliesToStringCollection(_initialReferencedAssemblies, compilParams.ReferencedAssemblies);
// Make any fix up adjustments to the CompilerParameters to work around some issues
FixUpCompilerParameters(_compConfig, _compilerType.CodeDomProviderType, compilParams);
return compilParams;
}
static string s_vbImportsString;
private static void AddVBGlobalNamespaceImports(CompilerParameters compilParams) {
// Put together the VB import string on demand
if (s_vbImportsString == null) {
PagesSection pagesConfig = MTConfigUtil.GetPagesAppConfig();
if (pagesConfig.Namespaces == null) {
s_vbImportsString = String.Empty;
}
else {
StringBuilder sb = new StringBuilder();
sb.Append("/imports:");
bool nextItemNeedsComma = false;
// Auto-import Microsoft.VisualBasic is needed
if (pagesConfig.Namespaces.AutoImportVBNamespace) {
sb.Append("Microsoft.VisualBasic");
nextItemNeedsComma = true;
}
// Add all the namespaces from the config <namespaces> section
foreach (NamespaceInfo entry in pagesConfig.Namespaces) {
// If there was a previous entry, we need a comma separator
if (nextItemNeedsComma)
sb.Append(',');
sb.Append(entry.Namespace);
nextItemNeedsComma = true;
}
s_vbImportsString = sb.ToString();
}
}
// Prepend it to the compilerOptions
if (s_vbImportsString.Length > 0) {
if (compilParams.CompilerOptions == null)
compilParams.CompilerOptions = s_vbImportsString;
else
compilParams.CompilerOptions = s_vbImportsString + " " + compilParams.CompilerOptions;
}
}
// Command line string for My.* support
private const string MySupport = @"/define:_MYTYPE=\""Web\""";
private static void AddVBMyFlags(CompilerParameters compilParams) {
// Prepend it to the compilerOptions
if (compilParams.CompilerOptions == null)
compilParams.CompilerOptions = MySupport;
else
compilParams.CompilerOptions = MySupport + " " + compilParams.CompilerOptions;
}
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Warnings was about use of CompilerParameters.CoreAssemblyFileName which was not set by user supplied string - so okay.")]
internal static void FixUpCompilerParameters(CompilationSection compilationSection, Type codeDomProviderType, CompilerParameters compilParams) {
// The mscorlib reference is special cased, and needs to be passed via the CoreAssemblyFileName property.
if (BuildManagerHost.InClientBuildManager && !MultiTargetingUtil.IsTargetFramework20 && !MultiTargetingUtil.IsTargetFramework35) {
string coreAssemblyFile;
AssemblyResolver.GetPathToReferenceAssembly(typeof(string).Assembly, out coreAssemblyFile);
compilParams.CoreAssemblyFileName = coreAssemblyFile;
}
// DevDiv 404267: If the developer enabled 'warnings as errors', we should disable [Obsolete] warnings. This helps
// prevent in-place framework updates from breaking runtime compilation of pages. We only respect this attribute
// when not in the CBM, as CBM is a design-time feature instead of a runtime feature, and the developer probably
// wants to be notified of all errors at design time.
bool disableObsoleteWarnings = !BuildManagerHost.InClientBuildManager && compilationSection.DisableObsoleteWarnings;
// If C#, remove the warning that complains about variables that start with "__"
// Also ignore warning that complains about assemblyKeyName and delaysign
// Also ignore warning about assuming assembly versions matching (CS1701, DevDiv 137847, warning about System.Web.Extensions v1.0 matching v3.5)
if (codeDomProviderType == typeof(Microsoft.CSharp.CSharpCodeProvider)) {
List<string> noWarnStrings = new List<string>(5);
noWarnStrings.AddRange(new string[] { "1659", "1699", "1701" });
if (disableObsoleteWarnings) {
noWarnStrings.Add("612"); // [Obsolete] without message
noWarnStrings.Add("618"); // [Obsolete("with message")]
}
CodeDomUtility.PrependCompilerOption(compilParams, "/nowarn:" + String.Join(";", noWarnStrings));
}
else if (codeDomProviderType == typeof(Microsoft.VisualBasic.VBCodeProvider)) {
List<string> noWarnStrings = new List<string>(3);
// If VB, add all the imported namespaces on the command line (DevDiv 21499).
// This is VB only because other languages don't support global command line
// namespace imports.
AddVBGlobalNamespaceImports(compilParams);
// Add any command line flags needed to support the My.* feature
AddVBMyFlags(compilParams);
// Ignore vb warning that complains about assemblyKeyName (Dev10 662544)
// but only for target 3.5 and above (715329)
if (MultiTargetingUtil.TargetFrameworkVersion >= MultiTargetingUtil.Version35) {
noWarnStrings.Add("41008");
}
if (disableObsoleteWarnings) {
noWarnStrings.Add("40000"); // [Obsolete("with message")]
noWarnStrings.Add("40008"); // [Obsolete] without message
}
if (noWarnStrings.Count > 0) {
CodeDomUtility.PrependCompilerOption(compilParams, "/nowarn:" + String.Join(",", noWarnStrings));
}
}
ProcessProviderOptions(codeDomProviderType, compilParams);
FixTreatWarningsAsErrors(codeDomProviderType, compilParams);
// Add CodeAnalysis symbol if required by client.
if (BuildManager.PrecompilingWithCodeAnalysisSymbol) {
CodeDomUtility.PrependCompilerOption(compilParams, "/define:CODE_ANALYSIS");
}
}
// DevDiv 114316
// CodeDom sets TreatWarningAsErrors to true whenever warningLevel is non-zero.
// To get warnings only, the workaround is to use /warnaserror- in CompilerOptions.
// However this does not work in some cases, as TreatWarningAsErrors set to true still emits
// /warnaserror+.
// So, whenever the user wants /warnaserror[+|-|numberlist], we explicitly set TreatWarningsAsErrors to false,
// so that the /warnaserror+ is not emitted, and the user can specify exactly what is desired.
internal static void FixTreatWarningsAsErrors(Type codeDomProviderType, CompilerParameters compilParams) {
// Only do so for C# and VB.
if (codeDomProviderType != typeof(Microsoft.CSharp.CSharpCodeProvider) &&
codeDomProviderType != typeof(Microsoft.VisualBasic.VBCodeProvider))
return;
if (CultureInfo.InvariantCulture.CompareInfo.IndexOf(compilParams.CompilerOptions, "/warnaserror", CompareOptions.IgnoreCase) >= 0)
compilParams.TreatWarningsAsErrors = false;
}
// Check for OptionInfer and WarnAsError. This is the workaround as use of compilerOptions is not allowed in partial trust.
// Devdiv 130325
//
private static void ProcessProviderOptions(Type codeDomProviderType, CompilerParameters compilParams) {
IDictionary<string, string> providerOptions = CompilationUtil.GetProviderOptions(codeDomProviderType);
if (providerOptions == null) return;
// For C# and VB, check for WarnAsError
if (codeDomProviderType == typeof(Microsoft.VisualBasic.VBCodeProvider) ||
codeDomProviderType == typeof(Microsoft.CSharp.CSharpCodeProvider))
ProcessBooleanProviderOption("WarnAsError", "/warnaserror+", "/warnaserror-", providerOptions, compilParams);
// Only process OptionInfer for v3.5 compiler (or above)
if (codeDomProviderType == null || !CompilationUtil.IsCompilerVersion35OrAbove(codeDomProviderType))
return;
// For VB, check for OptionInfer
if (codeDomProviderType == typeof(Microsoft.VisualBasic.VBCodeProvider))
ProcessBooleanProviderOption("OptionInfer", "/optionInfer+", "/optionInfer-", providerOptions, compilParams);
}
private static void ProcessBooleanProviderOption(string providerOptionName, string trueCompilerOption, string falseCompilerOption,
IDictionary<string, string> providerOptions, CompilerParameters compilParams) {
if (providerOptions == null || compilParams == null) return;
Debug.Assert(providerOptionName != null, "providerOptionName should not be null");
Debug.Assert(trueCompilerOption != null, "trueCompilerOption should not be null");
Debug.Assert(falseCompilerOption != null, "falseCompilerOption should not be null");
string providerOptionValue = null;
if (!providerOptions.TryGetValue(providerOptionName, out providerOptionValue)) return;
if (string.IsNullOrEmpty(providerOptionValue))
throw new System.Configuration.ConfigurationErrorsException(SR.GetString(SR.Property_NullOrEmpty, CompilationUtil.CodeDomProviderOptionPath + providerOptionName));
bool value;
if (Boolean.TryParse(providerOptionValue, out value)) {
// If the value is boolean, insert the compiler options
if (value)
CodeDomUtility.AppendCompilerOption(compilParams, trueCompilerOption);
else
CodeDomUtility.AppendCompilerOption(compilParams, falseCompilerOption);
}
else {
// If the value is not boolean, throw an exception
throw new System.Configuration.ConfigurationErrorsException(SR.GetString(SR.Value_must_be_boolean, CompilationUtil.CodeDomProviderOptionPath + providerOptionName));
}
}
internal CompilerResults Compile() {
// First, check if there is something to compile
if (_sourceFiles.Count == 0 && _embeddedResourceFiles == null)
return null;
// if we have some fast object factories to generate, get the CodeCompileUnit
if (_objectFactoryGenerator != null) {
_miscCodeCompileUnit = _objectFactoryGenerator.CodeCompileUnit;
}
// Add a culture attribute if needed
AddAssemblyCultureAttribute();
// Add a ComVisible(false) attribute (VSWhidbey 436453)
// Actually, don't do it to avoid breaking migrated apps (VSWhidbey 446788)
//AddComVisibleAttribute();
// Add an AspNetGeneratedCode attribute to help fxcop ignore some violations (VSWhidbey 437581)
AddAspNetGeneratedCodeAttribute();
// Add an AllowPartiallyTrustedCallers attribute to make strong-name assemblies. (Devdiv 39696)
AddAllowPartiallyTrustedCallersAttribute();
AddAssemblyDelaySignAttribute();
AddAssemblyKeyFileAttribute();
AddAssemblyKeyContainerAttribute();
AddSecurityRulesAttribute();
AddTargetFrameworkAttribute();
// Generate a source file for the misc top level items if needed
GenerateMiscCodeCompileUnit();
CompilerParameters compilParams = GetCompilerParameters();
string[] files = new string[_sourceFiles.Count];
_sourceFiles.CopyTo(files, 0);
// Increment compilation counter
PerfCounters.IncrementCounter(AppPerfCounter.COMPILATIONS);
// Raise Web Event
WebBaseEvent.RaiseSystemEvent(this, WebEventCodes.ApplicationCompilationStart);
HttpContext context = HttpContext.Current;
if (context != null) {
if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_COMPILE_ENTER, context.WorkerRequest);
}
CompilerResults results = null;
try {
try {
// Revert impersonation when compiling source code in the codegen dir (VSWhidbey 176576)
using (new ProcessImpersonationContext()) {
results = _codeProvider.CompileAssemblyFromFile(compilParams, files);
}
}
finally {
if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure) && context != null) {
string fileNames = null;
if (_buildProviders.Count < 20) {
IDictionaryEnumerator e = _buildProviders.GetEnumerator();
while(e.MoveNext()) {
if (fileNames != null)
fileNames += ",";
fileNames += e.Key;
}
}
else {
fileNames = String.Format(CultureInfo.InstalledUICulture, SR.Resources.GetString(SR.Etw_Batch_Compilation, CultureInfo.InstalledUICulture), new object[1] {_buildProviders.Count});
}
string status;
if (results != null && (results.NativeCompilerReturnValue != 0 || results.Errors.HasErrors))
status = SR.Resources.GetString(SR.Etw_Failure, CultureInfo.InstalledUICulture);
else
status = SR.Resources.GetString(SR.Etw_Success, CultureInfo.InstalledUICulture);
EtwTrace.Trace(EtwTraceType.ETW_TYPE_COMPILE_LEAVE, context.WorkerRequest, fileNames, status);
}
}
}
catch { throw; } // Prevent Exception Filter Security Issue (ASURT 122835)
// If an IAssemblyPostProcessor is registered, call it
Type postProcessorType = _compConfig.AssemblyPostProcessorTypeInternal;
if (postProcessorType != null) {
using (IAssemblyPostProcessor postProcessor = (IAssemblyPostProcessor) HttpRuntime.FastCreatePublicInstance(postProcessorType)) {
postProcessor.PostProcessAssembly(results.PathToAssembly);
}
}
// Raise Web Event
WebBaseEvent.RaiseSystemEvent(this, WebEventCodes.ApplicationCompilationEnd);
if (results != null) {
// Invalidate an invalid assembly to trigger recompilation
InvalidateInvalidAssembly(results, compilParams);
// Fix up with line pragmas to account for the http case, and for some special conditions
FixUpLinePragmas(results);
if (results.Errors.HasErrors) {
// Give all the BuildProviders a chance to look at the compile errors, and possibly tweak them
foreach (BuildProvider buildProvider in BuildProviders) {
buildProvider.ProcessCompileErrors(results);
}
}
// If there is a CBM callback, inform it of the errors/warnings
if (BuildManager.CBMCallback != null) {
foreach (CompilerError error in results.Errors) {
BuildManager.CBMCallback.ReportCompilerError(error);
}
}
// If there are errors, increment the relevant perf counters and throw
if (results.NativeCompilerReturnValue != 0 || results.Errors.HasErrors) {
// Increment the compilation error and total error counters
PerfCounters.IncrementCounter(AppPerfCounter.ERRORS_COMPILING);
PerfCounters.IncrementCounter(AppPerfCounter.ERRORS_TOTAL);
throw new HttpCompileException(results, GetErrorSourceFileContents(results));
}
}
return results;
}
private void InvalidateInvalidAssembly(CompilerResults results, CompilerParameters compilParams) {
// VSWhidbey 610291
// If target assembly gets locked, we invalidate the assembly, so that it does
// not get used, and the next compilation will use a new assembly name.
// CS0016 is the error code for "Could not write to output file 'file' -- 'reason'"
if (results == null || !results.Errors.HasErrors)
return;
foreach (CompilerError error in results.Errors) {
if (error.IsWarning) continue;
if (StringUtil.EqualsIgnoreCase(error.ErrorNumber, "CS0016")){
// Also invalidate the base assembly if this is a localized resource assembly
if (CultureName != null) {
string dir = _tempFiles.TempDir;
string baseAssemblyFile = Path.Combine(dir, OutputAssemblyName + ".dll");
DiskBuildResultCache.TryDeleteFile(new FileInfo(baseAssemblyFile));
}
// Invalidate the target assembly
DiskBuildResultCache.TryDeleteFile(compilParams.OutputAssembly);
}
}
}
/*
* Fix up all the source files in the errors in case they are HTTP (VS compiler scenario).
* Also, fix the error in case the base class was incorrect in the code beside model
*/
private void FixUpLinePragmas(CompilerResults results) {
CompilerError badBaseClassError = null;
// Go through the errors backwards so we can delete them as needed
for (int i=results.Errors.Count-1; i>=0; i--) {
CompilerError error = results.Errors[i];
string physicalPath = ErrorFormatter.ResolveHttpFileName(error.FileName);
// Only replace it by the physical path if it actually exists, which may not
// be the case when using a VirtualPathProvider
if (File.Exists(physicalPath)) {
error.FileName = physicalPath;
// If it is our special marker line number, remember it and remove it.
// We place the marker at two places: 1) before setting AppRelativeVirtualPath in the constructor,
// and 2) before the method FrameworkInitialize.
// For the generated method FrameworkInitialize, the method comes one line after
// the marker, due to an additional line taken by the DebuggerNonUserCodeAttribute. (DevDiv 175681)
if (error.Line == TemplateControlCodeDomTreeGenerator.badBaseClassLineMarker ||
(error.Line == TemplateControlCodeDomTreeGenerator.badBaseClassLineMarker + 1 &&
error.ErrorText != null && error.ErrorText.IndexOf("FrameworkInitialize", StringComparison.OrdinalIgnoreCase) >= 0)) {
badBaseClassError = error;
results.Errors.RemoveAt(i);
}
else if (error.Line > TemplateControlCodeDomTreeGenerator.badBaseClassLineMarker &&
error.Line < TemplateControlCodeDomTreeGenerator.badBaseClassLineMarker + 50) {
// Also, if within range of it, remove it altogether
results.Errors.RemoveAt(i);
}
}
}
// If we found our special marker error, we're most likely in a situation where
// the class in the code beside file doesn't match the 'inherits' in the aspx/ascx,
// or is missing the based type (or has the wrong base type). In that case, change
// the error message to make the problem explicit to the user (VSWhidbey 376977/468830)
if (badBaseClassError != null) {
// Read the content of the code beside file
string codeFileContent = Util.StringFromFile(badBaseClassError.FileName);
// Search for the partial class declaration within the file. We do this by searching for
// the string "partial class" in case insensitive way. This is far from fool proof, but
// it covers the common VB and C# cases, and the fallback when not found is reasonable.
int classOffset = CultureInfo.InvariantCulture.CompareInfo.IndexOf(codeFileContent,
"partial class", CompareOptions.IgnoreCase);
if (classOffset >= 0) {
// We found it, so figure out the line number from it
badBaseClassError.Line = Util.LineCount(codeFileContent, 0, classOffset) + 1;
}
else {
// Otherwise, just use 1. It won't point to the right line, but at least the error
// message is helpful
badBaseClassError.Line = 1;
}
// Change the error message to make the situation clear to the user
badBaseClassError.ErrorText = SR.GetString(SR.Bad_Base_Class_In_Code_File);
badBaseClassError.ErrorNumber = "ASPNET";
// Insert the error at the begining of the collection, since we display the first error.
results.Errors.Insert(0, badBaseClassError);
}
}
/*
* Attempt to find the generated source file that has the error, and return
* its contents as a string (for error reproting purposes).
* Note that when debug is false, we set tempFiles.KeepFiles to false, and
* all the sources will be gone by the time we get here. I filed VSWhidbey 103673,
* to get a solution to this from BCL.
*/
private string GetErrorSourceFileContents(CompilerResults results) {
if (!results.Errors.HasErrors)
return null;
// Get the physical path of the file that has the error. Note that this could be
// either the path to a high level file (e.g. aspx) if pragmas are in play,
// or the path to a generated file if there are no pragmas
string linePragma = results.Errors[0].FileName;
// Attempt to locate the correct build provider
BuildProvider buildProvider = GetBuildProviderFromLinePragma(linePragma);
if (buildProvider != null) {
// Return the generated file for this build provider
return GetGeneratedSourceFromBuildProvider(buildProvider);
}
// If we didn't find it, then we're probably in the no pragma case, in
// which case linePragma itself is the generated file
return Util.StringFromFileIfExists(linePragma);
}
internal string GetGeneratedSourceFromBuildProvider(BuildProvider buildProvider) {
// Return the generated file content for this build provider
string generatedFilePath = (string) _buildProviderToSourceFileMap[buildProvider];
return Util.StringFromFileIfExists(generatedFilePath);
}
internal BuildProvider GetBuildProviderFromLinePragma(string linePragma) {
BuildProvider buildProvider = GetBuildProviderFromLinePragmaInternal(linePragma);
// If it's a CompileWith provider, return the main provider instead
SourceFileBuildProvider sourceBuildProvider = buildProvider as SourceFileBuildProvider;
if (sourceBuildProvider != null)
buildProvider = sourceBuildProvider.OwningBuildProvider;
return buildProvider;
}
private BuildProvider GetBuildProviderFromLinePragmaInternal(string linePragma) {
// If we didn't keep track of any generated files, we can't do much
if (_buildProviderToSourceFileMap == null)
return null;
// Check if it's an http line pragma, from which we can get a VirtualPath
string virtualPath = ErrorFormatter.GetVirtualPathFromHttpLinePragma(linePragma);
// First, look for the pragma case
foreach (BuildProvider buildProvider in BuildProviders) {
// If the build provider can't give us a virtual path, skip it
if (buildProvider.VirtualPath == null)
continue;
// If we got a virtual path, use it to locate the correct BuildProvider
if (virtualPath != null) {
if (StringUtil.EqualsIgnoreCase(virtualPath, buildProvider.VirtualPath)) {
return buildProvider;
}
continue;
}
// Otherwise, work with the physical path
string physicalPath = HostingEnvironment.MapPathInternal(buildProvider.VirtualPath);
if (StringUtil.EqualsIgnoreCase(linePragma, physicalPath)) {
return buildProvider;
}
}
return null;
}
}
/*
* This class is used intead of AssemblyBuilder when handling
* ClientBuildManager.GetCodeDirectoryInformation
* It is instantiated via CompilerType.CreateAssemblyBuilder.
*/
internal class CbmCodeGeneratorBuildProviderHost: AssemblyBuilder {
private string _generatedFilesDir;
internal CbmCodeGeneratorBuildProviderHost(CompilationSection compConfig,
ICollection referencedAssemblies, CompilerType compilerType,
string generatedFilesDir, string outputAssemblyName)
: base(compConfig, referencedAssemblies, compilerType, outputAssemblyName) {
// Wipe out any existing directory, and recreate it
// This is where we will put generated source files
if (Directory.Exists(generatedFilesDir)) {
// Delete all the files in the directory
foreach (FileData fileData in FileEnumerator.Create(generatedFilesDir)) {
// It should only contain files
Debug.Assert(!fileData.IsDirectory);
if (fileData.IsDirectory) continue;
Debug.Trace("CbmCodeGeneratorBuildProviderHost", "Deleting " + fileData.FullName);
File.Delete(fileData.FullName);
}
}
// Create it to make sure it exists
Directory.CreateDirectory(generatedFilesDir);
_generatedFilesDir = generatedFilesDir;
}
internal override TextWriter CreateCodeFile(BuildProvider buildProvider, out string filename) {
// use GetCacheKeyFromVirtualPath to get a file name that looks like
// the original file, but is guaranteed unique across different virtual dirs.
string generatedCodeFile = BuildManager.GetCacheKeyFromVirtualPath(
buildProvider.VirtualPathObject);
generatedCodeFile = Path.Combine(_generatedFilesDir, generatedCodeFile);
generatedCodeFile = FileUtil.TruncatePathIfNeeded(generatedCodeFile, 10 /*length of extension */);
generatedCodeFile = generatedCodeFile + "." + _codeProvider.FileExtension;
filename = generatedCodeFile;
BuildManager.GenerateFileTable[buildProvider.VirtualPathObject.VirtualPathStringNoTrailingSlash] = generatedCodeFile;
Debug.Trace("CbmCodeGeneratorBuildProviderHost", "Generating " + generatedCodeFile);
Stream temp = new FileStream(generatedCodeFile, FileMode.Create, FileAccess.Write, FileShare.Read);
return new StreamWriter(temp, Encoding.UTF8);
}
internal override void AddBuildProvider(BuildProvider buildProvider) {
// Skip source files, since their code generation is an identity transform
if (buildProvider is SourceFileBuildProvider)
return;
base.AddBuildProvider(buildProvider);
}
}
}
|