Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System / compmod / system / codedom / compiler / CodeCompiler.cs
blob45fb83a1ea79b5b511cea2bc55ff7f2e6fa914df
1 //------------------------------------------------------------------------------
2 // <copyright file="CodeCompiler.cs" company="Microsoft">
3 //
4 // <OWNER>Microsoft</OWNER>
5 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // </copyright>
7 //------------------------------------------------------------------------------
9 namespace System.CodeDom.Compiler {
10 using System.Text;
12 using System.Diagnostics;
13 using System;
14 using Microsoft.Win32;
15 using Microsoft.Win32.SafeHandles;
16 using System.IO;
17 using System.Collections;
18 using System.Security;
19 using System.Security.Permissions;
20 using System.Security.Principal;
21 using System.Reflection;
22 using System.CodeDom;
23 using System.Globalization;
24 using System.Runtime.Versioning;
26 /// <devdoc>
27 /// <para>Provides a
28 /// base
29 /// class for code compilers.</para>
30 /// </devdoc>
31 [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")]
32 [PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust")]
33 public abstract class CodeCompiler : CodeGenerator, ICodeCompiler {
35 /// <internalonly/>
36 CompilerResults ICodeCompiler.CompileAssemblyFromDom(CompilerParameters options, CodeCompileUnit e) {
37 if( options == null) {
38 throw new ArgumentNullException("options");
41 try {
42 return FromDom(options, e);
44 finally {
45 options.TempFiles.SafeDelete();
49 /// <internalonly/>
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");
57 try {
58 return FromFile(options, fileName);
60 finally {
61 options.TempFiles.SafeDelete();
65 /// <internalonly/>
66 CompilerResults ICodeCompiler.CompileAssemblyFromSource(CompilerParameters options, string source) {
67 if( options == null) {
68 throw new ArgumentNullException("options");
71 try {
72 return FromSource(options, source);
74 finally {
75 options.TempFiles.SafeDelete();
79 /// <internalonly/>
80 CompilerResults ICodeCompiler.CompileAssemblyFromSourceBatch(CompilerParameters options, string[] sources) {
81 if( options == null) {
82 throw new ArgumentNullException("options");
85 try {
86 return FromSourceBatch(options, sources);
88 finally {
89 options.TempFiles.SafeDelete();
93 /// <internalonly/>
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");
103 try {
104 // Try opening the files to make sure they exists. This will throw an exception
105 // if it doesn't
106 foreach (string fileName in fileNames) {
107 using (Stream str = File.OpenRead(fileName)) { }
110 return FromFileBatch(options, fileNames);
112 finally {
113 options.TempFiles.SafeDelete();
117 /// <internalonly/>
118 CompilerResults ICodeCompiler.CompileAssemblyFromDomBatch(CompilerParameters options, CodeCompileUnit[] ea) {
119 if( options == null) {
120 throw new ArgumentNullException("options");
123 try {
124 return FromDomBatch(options, ea);
126 finally {
127 options.TempFiles.SafeDelete();
131 /// <devdoc>
132 /// <para>
133 /// Gets
134 /// or sets the file extension to use for source files.
135 /// </para>
136 /// </devdoc>
137 protected abstract string FileExtension {
138 get;
141 /// <devdoc>
142 /// <para>Gets or
143 /// sets the name of the compiler executable.</para>
144 /// </devdoc>
145 protected abstract string CompilerName {
146 get;
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);
164 else {
165 throw new InvalidOperationException(SR.GetString(SR.CompilerNotFound, fullname));
169 /// <devdoc>
170 /// <para>
171 /// Compiles the specified compile unit and options, and returns the results
172 /// from the compilation.
173 /// </para>
174 /// </devdoc>
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];
182 units[0] = e;
183 return FromDomBatch(options, units);
186 /// <devdoc>
187 /// <para>
188 /// Compiles the specified file using the specified options, and returns the
189 /// results from the compilation.
190 /// </para>
191 /// </devdoc>
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
204 // if it doesn't
205 using (Stream str = File.OpenRead(fileName)) { }
207 string[] filenames = new string[1];
208 filenames[0] = fileName;
209 return FromFileBatch(options, filenames);
212 /// <devdoc>
213 /// <para>
214 /// Compiles the specified source code using the specified options, and
215 /// returns the results from the compilation.
216 /// </para>
217 /// </devdoc>
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];
226 sources[0] = source;
228 return FromSourceBatch(options, sources);
231 /// <devdoc>
232 /// <para>
233 /// Compiles the specified compile units and
234 /// options, and returns the results from the compilation.
235 /// </para>
236 /// </devdoc>
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");
243 if (ea == null)
244 throw new ArgumentNullException("ea");
246 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
248 string[] filenames = new string[ea.Length];
250 CompilerResults results = null;
252 #if !FEATURE_PAL
253 // the extra try-catch is here to mitigate exception filter injection attacks.
254 try {
255 WindowsImpersonationContext impersonation = Executor.RevertImpersonation();
256 try {
257 #endif // !FEATURE_PAL
258 for (int i = 0; i < ea.Length; i++) {
259 if (ea[i] == null)
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);
265 try {
266 using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)){
267 ((ICodeGenerator)this).GenerateCodeFromCompileUnit(ea[i], sw, Options);
268 sw.Flush();
271 finally {
272 temp.Close();
276 results = FromFileBatch(options, filenames);
277 #if !FEATURE_PAL
279 finally {
280 Executor.ReImpersonate(impersonation);
283 catch {
284 throw;
286 #endif // !FEATURE_PAL
287 return results;
290 /// <devdoc>
291 /// <para>
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.
295 /// </para>
296 /// </devdoc>
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);
307 /// <devdoc>
308 /// <para>
309 /// Compiles the specified files using the specified options, and returns the
310 /// results from the compilation.
311 /// </para>
312 /// </devdoc>
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;
325 int retValue = 0;
327 CompilerResults results = new CompilerResults(options.TempFiles);
328 SecurityPermission perm1 = new SecurityPermission(SecurityPermissionFlag.ControlEvidence);
329 perm1.Assert();
330 try {
331 #pragma warning disable 618
332 results.Evidence = options.Evidence;
333 #pragma warning restore 618
335 finally {
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;
351 #if FEATURE_PAL
352 results.TempFiles.AddExtension("ildb");
353 #else
354 results.TempFiles.AddExtension("pdb");
355 #endif
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) {
364 trueArgs = args;
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);
377 try {
378 if (outputStream.Length > 0) {
379 // The output of the compiler is in UTF8
380 StreamReader sr = new StreamReader(outputStream, Encoding.UTF8);
381 string line;
382 do {
383 line = sr.ReadLine();
384 if (line != null) {
385 results.Output.Add(line);
387 ProcessCompilerOutputLine(results, line);
389 } while (line != null);
392 finally {
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);
403 try {
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);
408 perm.Assert();
409 try {
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
414 finally {
415 SecurityPermission.RevertAssert();
418 finally {
419 fs.Close();
422 else {
424 results.PathToAssembly = options.OutputAssembly;
427 return results;
430 /// <devdoc>
431 /// <para>Processes the specified line from the specified <see cref='System.CodeDom.Compiler.CompilerResults'/> .</para>
432 /// </devdoc>
433 protected abstract void ProcessCompilerOutputLine(CompilerResults results, string line);
435 /// <devdoc>
436 /// <para>
437 /// Gets the command arguments from the specified <see cref='System.CodeDom.Compiler.CompilerParameters'/>.
438 /// </para>
439 /// </devdoc>
440 protected abstract string CmdArgsFromParameters(CompilerParameters options);
442 /// <devdoc>
443 /// <para>[To be supplied.]</para>
444 /// </devdoc>
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);
452 try {
453 using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)) {
454 sw.Write(cmdArgs);
455 sw.Flush();
458 finally {
459 temp.Close();
462 return "@\"" + responseFileName + "\"";
465 /// <devdoc>
466 /// <para>
467 /// Compiles the specified source code strings using the specified options, and
468 /// returns the results from the compilation.
469 /// </para>
470 /// </devdoc>
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");
477 if (sources == null)
478 throw new ArgumentNullException("sources");
480 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
482 string[] filenames = new string[sources.Length];
484 CompilerResults results = null;
485 #if !FEATURE_PAL
486 // the extra try-catch is here to mitigate exception filter injection attacks.
487 try {
488 WindowsImpersonationContext impersonation = Executor.RevertImpersonation();
489 try {
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);
494 try {
495 using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8)) {
496 sw.Write(sources[i]);
497 sw.Flush();
500 finally {
501 temp.Close();
503 filenames[i] = name;
505 results = FromFileBatch(options, filenames);
506 #if !FEATURE_PAL
508 finally {
509 Executor.ReImpersonate(impersonation);
512 catch {
513 throw;
515 #endif // !FEATURE_PAL
517 return results;
520 /// <devdoc>
521 /// <para>Joins the specified string arrays.</para>
522 /// </devdoc>
523 protected static string JoinStringArray(string[] sa, string separator) {
524 if (sa == null || sa.Length == 0)
525 return String.Empty;
527 if (sa.Length == 1) {
528 return "\"" + sa[0] + "\"";
531 StringBuilder sb = new StringBuilder();
532 for (int i = 0; i < sa.Length - 1; i++) {
533 sb.Append("\"");
534 sb.Append(sa[i]);
535 sb.Append("\"");
536 sb.Append(separator);
538 sb.Append("\"");
539 sb.Append(sa[sa.Length - 1]);
540 sb.Append("\"");
542 return sb.ToString();