1 //------------------------------------------------------------------------------
2 // <copyright file="CodeCompiler.cs" company="Microsoft">
4 // <OWNER>Microsoft</OWNER>
5 // Copyright (c) Microsoft Corporation. All rights reserved.
7 //------------------------------------------------------------------------------
9 namespace System.CodeDom.Compiler {
12 using System.Diagnostics;
14 using Microsoft.Win32;
15 using Microsoft.Win32.SafeHandles;
17 using System.Collections;
18 using System.Security;
19 using System.Security.Permissions;
20 using System.Security.Principal;
21 using System.Reflection;
23 using System.Globalization;
24 using System.Runtime.Versioning;
29 /// class for code compilers.</para>
31 [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")]
32 [PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust")]
33 public abstract class CodeCompiler : CodeGenerator, ICodeCompiler {
36 CompilerResults ICodeCompiler.CompileAssemblyFromDom(CompilerParameters options, CodeCompileUnit e) {
37 if( options == null) {
38 throw new ArgumentNullException("options");
42 return FromDom(options, e);
45 options.TempFiles.SafeDelete();
50 [ResourceExposure(ResourceScope.Machine)]
51 [ResourceConsumption(ResourceScope.Machine)]
52 CompilerResults ICodeCompiler.CompileAssemblyFromFile(CompilerParameters options, string fileName) {
53 if( options == null) {
54 throw new ArgumentNullException("options");
58 return FromFile(options, fileName);
61 options.TempFiles.SafeDelete();
66 CompilerResults ICodeCompiler.CompileAssemblyFromSource(CompilerParameters options, string source) {
67 if( options == null) {
68 throw new ArgumentNullException("options");
72 return FromSource(options, source);
75 options.TempFiles.SafeDelete();
80 CompilerResults ICodeCompiler.CompileAssemblyFromSourceBatch(CompilerParameters options, string[] sources) {
81 if( options == null) {
82 throw new ArgumentNullException("options");
86 return FromSourceBatch(options, sources);
89 options.TempFiles.SafeDelete();
94 [ResourceExposure(ResourceScope.Machine)]
95 [ResourceConsumption(ResourceScope.Machine)]
96 CompilerResults ICodeCompiler.CompileAssemblyFromFileBatch(CompilerParameters options, string[] fileNames) {
97 if( options == null) {
98 throw new ArgumentNullException("options");
100 if (fileNames == null)
101 throw new ArgumentNullException("fileNames");
104 // Try opening the files to make sure they exists. This will throw an exception
106 foreach (string fileName in fileNames) {
107 using (Stream str = File.OpenRead(fileName)) { }
110 return FromFileBatch(options, fileNames);
113 options.TempFiles.SafeDelete();
118 CompilerResults ICodeCompiler.CompileAssemblyFromDomBatch(CompilerParameters options, CodeCompileUnit[] ea) {
119 if( options == null) {
120 throw new ArgumentNullException("options");
124 return FromDomBatch(options, ea);
127 options.TempFiles.SafeDelete();
134 /// or sets the file extension to use for source files.
137 protected abstract string FileExtension {
143 /// sets the name of the compiler executable.</para>
145 protected abstract string CompilerName {
150 [ResourceExposure(ResourceScope.Machine)]
151 [ResourceConsumption(ResourceScope.Machine)]
152 internal void Compile(CompilerParameters options, string compilerDirectory, string compilerExe, string arguments, ref string outputFile, ref int nativeReturnValue, string trueArgs) {
153 string errorFile = null;
154 outputFile = options.TempFiles.AddExtension("out");
156 // We try to execute the compiler with a full path name.
157 string fullname = Path.Combine(compilerDirectory, compilerExe);
158 if (File.Exists(fullname)) {
159 string trueCmdLine = null;
160 if (trueArgs != null)
161 trueCmdLine = "\"" + fullname + "\" " + trueArgs;
162 nativeReturnValue = Executor.ExecWaitWithCapture(options.SafeUserToken, "\"" + fullname + "\" " + arguments, Environment.CurrentDirectory, options.TempFiles, ref outputFile, ref errorFile, trueCmdLine);
165 throw new InvalidOperationException(SR.GetString(SR.CompilerNotFound, fullname));
171 /// Compiles the specified compile unit and options, and returns the results
172 /// from the compilation.
175 protected virtual CompilerResults FromDom(CompilerParameters options, CodeCompileUnit e) {
176 if( options == null) {
177 throw new ArgumentNullException("options");
179 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
181 CodeCompileUnit[] units = new CodeCompileUnit[1];
183 return FromDomBatch(options, units);
188 /// Compiles the specified file using the specified options, and returns the
189 /// results from the compilation.
192 [ResourceExposure(ResourceScope.Machine)]
193 [ResourceConsumption(ResourceScope.Machine)]
194 protected virtual CompilerResults FromFile(CompilerParameters options, string fileName) {
195 if( options == null) {
196 throw new ArgumentNullException("options");
198 if (fileName == null)
199 throw new ArgumentNullException("fileName");
201 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
203 // Try opening the file to make sure it exists. This will throw an exception
205 using (Stream str = File.OpenRead(fileName)) { }
207 string[] filenames = new string[1];
208 filenames[0] = fileName;
209 return FromFileBatch(options, filenames);
214 /// Compiles the specified source code using the specified options, and
215 /// returns the results from the compilation.
218 protected virtual CompilerResults FromSource(CompilerParameters options, string source) {
219 if( options == null) {
220 throw new ArgumentNullException("options");
223 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
225 string[] sources = new string[1];
228 return FromSourceBatch(options, sources);
233 /// Compiles the specified compile units and
234 /// options, and returns the results from the compilation.
237 [ResourceExposure(ResourceScope.None)]
238 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
239 protected virtual CompilerResults FromDomBatch(CompilerParameters options, CodeCompileUnit[] ea) {
240 if( options == null) {
241 throw new ArgumentNullException("options");
244 throw new ArgumentNullException("ea");
246 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
248 string[] filenames = new string[ea.Length];
250 CompilerResults results = null;
253 // the extra try-catch is here to mitigate exception filter injection attacks.
255 WindowsImpersonationContext impersonation = Executor.RevertImpersonation();
257 #endif // !FEATURE_PAL
258 for (int i = 0; i < ea.Length; i++) {
260 continue; // the other two batch methods just work if one element is null, so we'll match that.
262 ResolveReferencedAssemblies(options, ea[i]);
263 filenames[i] = options.TempFiles.AddExtension(i + FileExtension);
264 Stream temp = new FileStream(filenames[i], FileMode.Create, FileAccess.Write, FileShare.Read);
266 using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)){
267 ((ICodeGenerator)this).GenerateCodeFromCompileUnit(ea[i], sw, Options);
276 results = FromFileBatch(options, filenames);
280 Executor.ReImpersonate(impersonation);
286 #endif // !FEATURE_PAL
292 /// Because CodeCompileUnit and CompilerParameters both have a referenced assemblies
293 /// property, they must be reconciled. However, because you can compile multiple
294 /// compile units with one set of options, it will simply merge them.
297 private void ResolveReferencedAssemblies(CompilerParameters options, CodeCompileUnit e) {
298 if (e.ReferencedAssemblies.Count > 0) {
299 foreach(string assemblyName in e.ReferencedAssemblies) {
300 if (!options.ReferencedAssemblies.Contains(assemblyName)) {
301 options.ReferencedAssemblies.Add(assemblyName);
309 /// Compiles the specified files using the specified options, and returns the
310 /// results from the compilation.
313 [ResourceExposure(ResourceScope.Machine)]
314 [ResourceConsumption(ResourceScope.Machine)]
315 protected virtual CompilerResults FromFileBatch(CompilerParameters options, string[] fileNames) {
316 if( options == null) {
317 throw new ArgumentNullException("options");
319 if (fileNames == null)
320 throw new ArgumentNullException("fileNames");
322 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
324 string outputFile = null;
327 CompilerResults results = new CompilerResults(options.TempFiles);
328 SecurityPermission perm1 = new SecurityPermission(SecurityPermissionFlag.ControlEvidence);
331 #pragma warning disable 618
332 results.Evidence = options.Evidence;
333 #pragma warning restore 618
336 SecurityPermission.RevertAssert();
338 bool createdEmptyAssembly = false;
340 if (options.OutputAssembly == null || options.OutputAssembly.Length == 0) {
341 string extension = (options.GenerateExecutable) ? "exe" : "dll";
342 options.OutputAssembly = results.TempFiles.AddExtension(extension, !options.GenerateInMemory);
344 // Create an empty assembly. This is so that the file will have permissions that
345 // we can later access with our current credential. If we don't do this, the compiler
346 // could end up creating an assembly that we cannot open
347 new FileStream(options.OutputAssembly, FileMode.Create, FileAccess.ReadWrite).Close();
348 createdEmptyAssembly = true;
352 results.TempFiles.AddExtension("ildb");
354 results.TempFiles.AddExtension("pdb");
358 string args = CmdArgsFromParameters(options) + " " + JoinStringArray(fileNames, " ");
360 // Use a response file if the compiler supports it
361 string responseFileArgs = GetResponseFileCmdArgs(options, args);
362 string trueArgs = null;
363 if (responseFileArgs != null) {
365 args = responseFileArgs;
368 Compile(options, Executor.GetRuntimeInstallDirectory(), CompilerName, args, ref outputFile, ref retValue, trueArgs);
370 results.NativeCompilerReturnValue = retValue;
372 // only look for errors/warnings if the compile failed or the caller set the warning level
373 if (retValue != 0 || options.WarningLevel > 0) {
375 FileStream outputStream = new FileStream(outputFile, FileMode.Open,
376 FileAccess.Read, FileShare.ReadWrite);
378 if (outputStream.Length > 0) {
379 // The output of the compiler is in UTF8
380 StreamReader sr = new StreamReader(outputStream, Encoding.UTF8);
383 line = sr.ReadLine();
385 results.Output.Add(line);
387 ProcessCompilerOutputLine(results, line);
389 } while (line != null);
393 outputStream.Close();
396 // Delete the empty assembly if we created one
397 if (retValue != 0 && createdEmptyAssembly)
398 File.Delete(options.OutputAssembly);
401 if (!results.Errors.HasErrors && options.GenerateInMemory) {
402 FileStream fs = new FileStream(options.OutputAssembly, FileMode.Open, FileAccess.Read, FileShare.Read);
404 int fileLen = (int)fs.Length;
405 byte[] b = new byte[fileLen];
406 fs.Read(b, 0, fileLen);
407 SecurityPermission perm = new SecurityPermission(SecurityPermissionFlag.ControlEvidence);
410 #pragma warning disable 618 // Load with evidence is obsolete - this warning is passed on via the options parameter
411 results.CompiledAssembly = Assembly.Load(b,null,options.Evidence);
412 #pragma warning restore 618
415 SecurityPermission.RevertAssert();
424 results.PathToAssembly = options.OutputAssembly;
431 /// <para>Processes the specified line from the specified <see cref='System.CodeDom.Compiler.CompilerResults'/> .</para>
433 protected abstract void ProcessCompilerOutputLine(CompilerResults results, string line);
437 /// Gets the command arguments from the specified <see cref='System.CodeDom.Compiler.CompilerParameters'/>.
440 protected abstract string CmdArgsFromParameters(CompilerParameters options);
443 /// <para>[To be supplied.]</para>
445 [ResourceExposure(ResourceScope.None)]
446 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
447 protected virtual string GetResponseFileCmdArgs(CompilerParameters options, string cmdArgs) {
449 string responseFileName = options.TempFiles.AddExtension("cmdline");
451 Stream temp = new FileStream(responseFileName, FileMode.Create, FileAccess.Write, FileShare.Read);
453 using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)) {
462 return "@\"" + responseFileName + "\"";
467 /// Compiles the specified source code strings using the specified options, and
468 /// returns the results from the compilation.
471 [ResourceExposure(ResourceScope.None)]
472 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
473 protected virtual CompilerResults FromSourceBatch(CompilerParameters options, string[] sources) {
474 if( options == null) {
475 throw new ArgumentNullException("options");
478 throw new ArgumentNullException("sources");
480 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
482 string[] filenames = new string[sources.Length];
484 CompilerResults results = null;
486 // the extra try-catch is here to mitigate exception filter injection attacks.
488 WindowsImpersonationContext impersonation = Executor.RevertImpersonation();
490 #endif // !FEATURE_PAL
491 for (int i = 0; i < sources.Length; i++) {
492 string name = options.TempFiles.AddExtension(i + FileExtension);
493 Stream temp = new FileStream(name, FileMode.Create, FileAccess.Write, FileShare.Read);
495 using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)) {
496 sw.Write(sources[i]);
505 results = FromFileBatch(options, filenames);
509 Executor.ReImpersonate(impersonation);
515 #endif // !FEATURE_PAL
521 /// <para>Joins the specified string arrays.</para>
523 protected static string JoinStringArray(string[] sa, string separator) {
524 if (sa == null || sa.Length == 0)
527 if (sa.Length == 1) {
528 return "\"" + sa[0] + "\"";
531 StringBuilder sb = new StringBuilder();
532 for (int i = 0; i < sa.Length - 1; i++) {
536 sb.Append(separator);
539 sb.Append(sa[sa.Length - 1]);
542 return sb.ToString();