//------------------------------------------------------------------------------ // // // [....] // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.CodeDom.Compiler { using System.Text; using System.Diagnostics; using System; using Microsoft.Win32; using Microsoft.Win32.SafeHandles; using System.IO; using System.Collections; using System.Security; using System.Security.Permissions; using System.Security.Principal; using System.Reflection; using System.CodeDom; using System.Globalization; using System.Runtime.Versioning; /// /// Provides a /// base /// class for code compilers. /// [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")] [PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust")] public abstract class CodeCompiler : CodeGenerator, ICodeCompiler { /// CompilerResults ICodeCompiler.CompileAssemblyFromDom(CompilerParameters options, CodeCompileUnit e) { if( options == null) { throw new ArgumentNullException("options"); } try { return FromDom(options, e); } finally { options.TempFiles.SafeDelete(); } } /// [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] CompilerResults ICodeCompiler.CompileAssemblyFromFile(CompilerParameters options, string fileName) { if( options == null) { throw new ArgumentNullException("options"); } try { return FromFile(options, fileName); } finally { options.TempFiles.SafeDelete(); } } /// CompilerResults ICodeCompiler.CompileAssemblyFromSource(CompilerParameters options, string source) { if( options == null) { throw new ArgumentNullException("options"); } try { return FromSource(options, source); } finally { options.TempFiles.SafeDelete(); } } /// CompilerResults ICodeCompiler.CompileAssemblyFromSourceBatch(CompilerParameters options, string[] sources) { if( options == null) { throw new ArgumentNullException("options"); } try { return FromSourceBatch(options, sources); } finally { options.TempFiles.SafeDelete(); } } /// [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] CompilerResults ICodeCompiler.CompileAssemblyFromFileBatch(CompilerParameters options, string[] fileNames) { if( options == null) { throw new ArgumentNullException("options"); } if (fileNames == null) throw new ArgumentNullException("fileNames"); try { // Try opening the files to make sure they exists. This will throw an exception // if it doesn't foreach (string fileName in fileNames) { using (Stream str = File.OpenRead(fileName)) { } } return FromFileBatch(options, fileNames); } finally { options.TempFiles.SafeDelete(); } } /// CompilerResults ICodeCompiler.CompileAssemblyFromDomBatch(CompilerParameters options, CodeCompileUnit[] ea) { if( options == null) { throw new ArgumentNullException("options"); } try { return FromDomBatch(options, ea); } finally { options.TempFiles.SafeDelete(); } } /// /// /// Gets /// or sets the file extension to use for source files. /// /// protected abstract string FileExtension { get; } /// /// Gets or /// sets the name of the compiler executable. /// protected abstract string CompilerName { get; } [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] internal void Compile(CompilerParameters options, string compilerDirectory, string compilerExe, string arguments, ref string outputFile, ref int nativeReturnValue, string trueArgs) { string errorFile = null; outputFile = options.TempFiles.AddExtension("out"); // We try to execute the compiler with a full path name. string fullname = Path.Combine(compilerDirectory, compilerExe); if (File.Exists(fullname)) { string trueCmdLine = null; if (trueArgs != null) trueCmdLine = "\"" + fullname + "\" " + trueArgs; nativeReturnValue = Executor.ExecWaitWithCapture(options.SafeUserToken, "\"" + fullname + "\" " + arguments, Environment.CurrentDirectory, options.TempFiles, ref outputFile, ref errorFile, trueCmdLine); } else { throw new InvalidOperationException(SR.GetString(SR.CompilerNotFound, fullname)); } } /// /// /// Compiles the specified compile unit and options, and returns the results /// from the compilation. /// /// protected virtual CompilerResults FromDom(CompilerParameters options, CodeCompileUnit e) { if( options == null) { throw new ArgumentNullException("options"); } new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); CodeCompileUnit[] units = new CodeCompileUnit[1]; units[0] = e; return FromDomBatch(options, units); } /// /// /// Compiles the specified file using the specified options, and returns the /// results from the compilation. /// /// [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] protected virtual CompilerResults FromFile(CompilerParameters options, string fileName) { if( options == null) { throw new ArgumentNullException("options"); } if (fileName == null) throw new ArgumentNullException("fileName"); new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); // Try opening the file to make sure it exists. This will throw an exception // if it doesn't using (Stream str = File.OpenRead(fileName)) { } string[] filenames = new string[1]; filenames[0] = fileName; return FromFileBatch(options, filenames); } /// /// /// Compiles the specified source code using the specified options, and /// returns the results from the compilation. /// /// protected virtual CompilerResults FromSource(CompilerParameters options, string source) { if( options == null) { throw new ArgumentNullException("options"); } new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); string[] sources = new string[1]; sources[0] = source; return FromSourceBatch(options, sources); } /// /// /// Compiles the specified compile units and /// options, and returns the results from the compilation. /// /// [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] protected virtual CompilerResults FromDomBatch(CompilerParameters options, CodeCompileUnit[] ea) { if( options == null) { throw new ArgumentNullException("options"); } if (ea == null) throw new ArgumentNullException("ea"); new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); string[] filenames = new string[ea.Length]; CompilerResults results = null; #if !FEATURE_PAL // the extra try-catch is here to mitigate exception filter injection attacks. try { WindowsImpersonationContext impersonation = Executor.RevertImpersonation(); try { #endif // !FEATURE_PAL for (int i = 0; i < ea.Length; i++) { if (ea[i] == null) continue; // the other two batch methods just work if one element is null, so we'll match that. ResolveReferencedAssemblies(options, ea[i]); filenames[i] = options.TempFiles.AddExtension(i + FileExtension); Stream temp = new FileStream(filenames[i], FileMode.Create, FileAccess.Write, FileShare.Read); try { using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)){ ((ICodeGenerator)this).GenerateCodeFromCompileUnit(ea[i], sw, Options); sw.Flush(); } } finally { temp.Close(); } } results = FromFileBatch(options, filenames); #if !FEATURE_PAL } finally { Executor.ReImpersonate(impersonation); } } catch { throw; } #endif // !FEATURE_PAL return results; } /// /// /// Because CodeCompileUnit and CompilerParameters both have a referenced assemblies /// property, they must be reconciled. However, because you can compile multiple /// compile units with one set of options, it will simply merge them. /// /// private void ResolveReferencedAssemblies(CompilerParameters options, CodeCompileUnit e) { if (e.ReferencedAssemblies.Count > 0) { foreach(string assemblyName in e.ReferencedAssemblies) { if (!options.ReferencedAssemblies.Contains(assemblyName)) { options.ReferencedAssemblies.Add(assemblyName); } } } } /// /// /// Compiles the specified files using the specified options, and returns the /// results from the compilation. /// /// [ResourceExposure(ResourceScope.Machine)] [ResourceConsumption(ResourceScope.Machine)] protected virtual CompilerResults FromFileBatch(CompilerParameters options, string[] fileNames) { if( options == null) { throw new ArgumentNullException("options"); } if (fileNames == null) throw new ArgumentNullException("fileNames"); new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); string outputFile = null; int retValue = 0; CompilerResults results = new CompilerResults(options.TempFiles); SecurityPermission perm1 = new SecurityPermission(SecurityPermissionFlag.ControlEvidence); perm1.Assert(); try { #pragma warning disable 618 results.Evidence = options.Evidence; #pragma warning restore 618 } finally { SecurityPermission.RevertAssert(); } bool createdEmptyAssembly = false; if (options.OutputAssembly == null || options.OutputAssembly.Length == 0) { string extension = (options.GenerateExecutable) ? "exe" : "dll"; options.OutputAssembly = results.TempFiles.AddExtension(extension, !options.GenerateInMemory); // Create an empty assembly. This is so that the file will have permissions that // we can later access with our current credential. If we don't do this, the compiler // could end up creating an assembly that we cannot open new FileStream(options.OutputAssembly, FileMode.Create, FileAccess.ReadWrite).Close(); createdEmptyAssembly = true; } #if FEATURE_PAL results.TempFiles.AddExtension("ildb"); #else results.TempFiles.AddExtension("pdb"); #endif string args = CmdArgsFromParameters(options) + " " + JoinStringArray(fileNames, " "); // Use a response file if the compiler supports it string responseFileArgs = GetResponseFileCmdArgs(options, args); string trueArgs = null; if (responseFileArgs != null) { trueArgs = args; args = responseFileArgs; } Compile(options, Executor.GetRuntimeInstallDirectory(), CompilerName, args, ref outputFile, ref retValue, trueArgs); results.NativeCompilerReturnValue = retValue; // only look for errors/warnings if the compile failed or the caller set the warning level if (retValue != 0 || options.WarningLevel > 0) { FileStream outputStream = new FileStream(outputFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); try { if (outputStream.Length > 0) { // The output of the compiler is in UTF8 StreamReader sr = new StreamReader(outputStream, Encoding.UTF8); string line; do { line = sr.ReadLine(); if (line != null) { results.Output.Add(line); ProcessCompilerOutputLine(results, line); } } while (line != null); } } finally { outputStream.Close(); } // Delete the empty assembly if we created one if (retValue != 0 && createdEmptyAssembly) File.Delete(options.OutputAssembly); } if (!results.Errors.HasErrors && options.GenerateInMemory) { FileStream fs = new FileStream(options.OutputAssembly, FileMode.Open, FileAccess.Read, FileShare.Read); try { int fileLen = (int)fs.Length; byte[] b = new byte[fileLen]; fs.Read(b, 0, fileLen); SecurityPermission perm = new SecurityPermission(SecurityPermissionFlag.ControlEvidence); perm.Assert(); try { #pragma warning disable 618 // Load with evidence is obsolete - this warning is passed on via the options parameter results.CompiledAssembly = Assembly.Load(b,null,options.Evidence); #pragma warning restore 618 } finally { SecurityPermission.RevertAssert(); } } finally { fs.Close(); } } else { results.PathToAssembly = options.OutputAssembly; } return results; } /// /// Processes the specified line from the specified . /// protected abstract void ProcessCompilerOutputLine(CompilerResults results, string line); /// /// /// Gets the command arguments from the specified . /// /// protected abstract string CmdArgsFromParameters(CompilerParameters options); /// /// [To be supplied.] /// [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] protected virtual string GetResponseFileCmdArgs(CompilerParameters options, string cmdArgs) { string responseFileName = options.TempFiles.AddExtension("cmdline"); Stream temp = new FileStream(responseFileName, FileMode.Create, FileAccess.Write, FileShare.Read); try { using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)) { sw.Write(cmdArgs); sw.Flush(); } } finally { temp.Close(); } return "@\"" + responseFileName + "\""; } /// /// /// Compiles the specified source code strings using the specified options, and /// returns the results from the compilation. /// /// [ResourceExposure(ResourceScope.None)] [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] protected virtual CompilerResults FromSourceBatch(CompilerParameters options, string[] sources) { if( options == null) { throw new ArgumentNullException("options"); } if (sources == null) throw new ArgumentNullException("sources"); new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); string[] filenames = new string[sources.Length]; CompilerResults results = null; #if !FEATURE_PAL // the extra try-catch is here to mitigate exception filter injection attacks. try { WindowsImpersonationContext impersonation = Executor.RevertImpersonation(); try { #endif // !FEATURE_PAL for (int i = 0; i < sources.Length; i++) { string name = options.TempFiles.AddExtension(i + FileExtension); Stream temp = new FileStream(name, FileMode.Create, FileAccess.Write, FileShare.Read); try { using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)) { sw.Write(sources[i]); sw.Flush(); } } finally { temp.Close(); } filenames[i] = name; } results = FromFileBatch(options, filenames); #if !FEATURE_PAL } finally { Executor.ReImpersonate(impersonation); } } catch { throw; } #endif // !FEATURE_PAL return results; } /// /// Joins the specified string arrays. /// protected static string JoinStringArray(string[] sa, string separator) { if (sa == null || sa.Length == 0) return String.Empty; if (sa.Length == 1) { return "\"" + sa[0] + "\""; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < sa.Length - 1; i++) { sb.Append("\""); sb.Append(sa[i]); sb.Append("\""); sb.Append(separator); } sb.Append("\""); sb.Append(sa[sa.Length - 1]); sb.Append("\""); return sb.ToString(); } } }