Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System / compmod / system / codedom / compiler / CodeCompiler.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="CodeCompiler.cs" company="Microsoft">
3 // 
4 // <OWNER>Microsoft</OWNER>
5 //     Copyright (c) Microsoft Corporation.  All rights reserved.
6 // </copyright>                                                                
7 //------------------------------------------------------------------------------
8
9 namespace System.CodeDom.Compiler {
10     using System.Text;
11
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;
25
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 {
34
35         /// <internalonly/>
36         CompilerResults ICodeCompiler.CompileAssemblyFromDom(CompilerParameters options, CodeCompileUnit e) {
37             if( options == null) {
38                 throw new ArgumentNullException("options");
39             }
40
41             try {                
42                 return FromDom(options, e);
43             }
44             finally {
45                 options.TempFiles.SafeDelete();
46             }
47         }
48
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");
55             }
56
57             try {
58                 return FromFile(options, fileName);
59             }
60             finally {
61                 options.TempFiles.SafeDelete();
62             }
63         }
64
65         /// <internalonly/>
66         CompilerResults ICodeCompiler.CompileAssemblyFromSource(CompilerParameters options, string source) {
67             if( options == null) {
68                 throw new ArgumentNullException("options");
69             }
70
71             try {
72                 return FromSource(options, source);
73             }
74             finally {
75                 options.TempFiles.SafeDelete();
76             }
77         }
78
79         /// <internalonly/>
80         CompilerResults ICodeCompiler.CompileAssemblyFromSourceBatch(CompilerParameters options, string[] sources) {
81             if( options == null) {
82                 throw new ArgumentNullException("options");
83             }
84
85             try {
86                 return FromSourceBatch(options, sources);
87             }
88             finally {
89                 options.TempFiles.SafeDelete();
90             }
91         }
92         
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");
99             }
100             if (fileNames == null)
101                 throw new ArgumentNullException("fileNames");
102
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)) { }
108                 }
109
110                 return FromFileBatch(options, fileNames);
111             }
112             finally {
113                 options.TempFiles.SafeDelete();
114             }
115         }
116
117         /// <internalonly/>
118         CompilerResults ICodeCompiler.CompileAssemblyFromDomBatch(CompilerParameters options, CodeCompileUnit[] ea) {
119             if( options == null) {
120                 throw new ArgumentNullException("options");
121             }
122
123             try {
124                 return FromDomBatch(options, ea);
125             }
126             finally {
127                 options.TempFiles.SafeDelete();
128             }
129         }
130         
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;
139         }
140
141         /// <devdoc>
142         /// <para>Gets or
143         /// sets the name of the compiler executable.</para>
144         /// </devdoc>
145         protected abstract string CompilerName {
146             get;
147         }
148
149
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");
155             
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);
163             }
164             else {
165                 throw new InvalidOperationException(SR.GetString(SR.CompilerNotFound, fullname));
166             }
167         }
168
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");
178             }
179             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
180                         
181             CodeCompileUnit[] units = new CodeCompileUnit[1];
182             units[0] = e;
183             return FromDomBatch(options, units);
184         }
185
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");
197             }
198             if (fileName == null)
199                 throw new ArgumentNullException("fileName");
200
201             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
202             
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)) { }
206
207             string[] filenames = new string[1];
208             filenames[0] = fileName;
209             return FromFileBatch(options, filenames);
210         }
211         
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");
221              }
222
223             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
224
225             string[] sources = new string[1];
226             sources[0] = source;
227
228             return FromSourceBatch(options, sources);
229         }
230         
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");
242             }
243             if (ea == null)
244                 throw new ArgumentNullException("ea");
245
246             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
247
248             string[] filenames = new string[ea.Length];
249
250             CompilerResults results = null;
251             
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. 
261                         
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();
269                             }
270                         }
271                         finally {
272                             temp.Close();
273                         }
274                     }
275
276                     results = FromFileBatch(options, filenames);
277 #if !FEATURE_PAL
278                 }
279                 finally {
280                     Executor.ReImpersonate(impersonation);
281                 }
282             }
283             catch {
284                 throw;
285             }
286 #endif // !FEATURE_PAL
287             return results;
288         }
289
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);
302                     }
303                 }
304             }
305         }
306
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");
318             }
319             if (fileNames == null)
320                 throw new ArgumentNullException("fileNames");
321
322             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
323
324             string outputFile = null;
325             int retValue = 0;
326
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
334             }
335             finally {
336                  SecurityPermission.RevertAssert();
337             }
338             bool createdEmptyAssembly = false;
339
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);
343
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;
349             }
350
351 #if FEATURE_PAL
352             results.TempFiles.AddExtension("ildb");
353 #else
354             results.TempFiles.AddExtension("pdb");
355 #endif
356
357
358             string args = CmdArgsFromParameters(options) + " " + JoinStringArray(fileNames, " ");
359
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;
366             }
367
368             Compile(options, Executor.GetRuntimeInstallDirectory(), CompilerName, args, ref outputFile, ref retValue, trueArgs);
369
370             results.NativeCompilerReturnValue = retValue;
371
372             // only look for errors/warnings if the compile failed or the caller set the warning level
373             if (retValue != 0 || options.WarningLevel > 0) {
374
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);
386
387                                 ProcessCompilerOutputLine(results, line);
388                             }
389                         } while (line != null);
390                     }
391                 }
392                 finally {
393                     outputStream.Close();
394                 }
395
396                 // Delete the empty assembly if we created one
397                 if (retValue != 0 && createdEmptyAssembly)
398                     File.Delete(options.OutputAssembly);
399             }
400
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
413                     }
414                     finally {
415                        SecurityPermission.RevertAssert();
416                     }
417                 }
418                 finally {
419                     fs.Close();
420                 }
421             }
422             else {
423
424                 results.PathToAssembly = options.OutputAssembly;
425             }
426
427             return results;
428         }
429
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);
434
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);
441
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) {
448
449             string responseFileName = options.TempFiles.AddExtension("cmdline");
450
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();
456                 }
457             }
458             finally {
459                 temp.Close();
460             }
461
462             return "@\"" + responseFileName + "\"";
463         }
464
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");
476             }
477             if (sources == null)
478                 throw new ArgumentNullException("sources");
479
480             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
481
482             string[] filenames = new string[sources.Length];
483
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();
498                             }
499                         }
500                         finally {
501                             temp.Close();
502                         }
503                         filenames[i] = name;
504                    }
505                    results = FromFileBatch(options, filenames);
506 #if !FEATURE_PAL
507                 }
508                 finally {
509                     Executor.ReImpersonate(impersonation);
510                 }
511             }   
512             catch {
513                 throw;
514             }
515 #endif // !FEATURE_PAL
516
517             return results;
518         }
519
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;
526
527             if (sa.Length == 1) {
528                 return "\"" + sa[0] + "\"";
529             }
530
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);
537             }
538             sb.Append("\"");
539             sb.Append(sa[sa.Length - 1]);
540             sb.Append("\"");
541
542             return sb.ToString();
543         }
544     }
545 }