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();