1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 //-----------------------------------------------------------------------
4 // </copyright>
5 // <summary>Utility methods for classifying and handling exceptions.</summary>
6 //-----------------------------------------------------------------------
7 
8 #if BUILDINGAPPXTASKS
9 namespace Microsoft.Build.AppxPackage.Shared
10 #else
11 using System;
12 using System.Diagnostics;
13 using System.Diagnostics.CodeAnalysis;
14 using System.Globalization;
15 using System.IO;
16 using System.Linq;
17 using System.Reflection;
18 using System.Security;
19 using System.Threading;
20 using System.Xml;
21 #if FEATURE_VARIOUS_EXCEPTIONS
22 using System.Xml.Schema;
23 using System.Runtime.Serialization;
24 #endif
25 
26 namespace Microsoft.Build.Shared
27 #endif
28 {
29     /// <summary>
30     /// Utility methods for classifying and handling exceptions.
31     /// </summary>
32     internal static class ExceptionHandling
33     {
34         private static readonly string s_debugDumpPath;
35 
ExceptionHandling()36         static ExceptionHandling()
37         {
38             s_debugDumpPath = GetDebugDumpPath();
39         }
40 
41         /// <summary>
42         /// Gets the location of the directory used for diagnostic log files.
43         /// </summary>
44         /// <returns></returns>
GetDebugDumpPath()45         private static string GetDebugDumpPath()
46         {
47             string debugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH");
48             return !string.IsNullOrEmpty(debugPath)
49                     ? debugPath
50                     : Path.GetTempPath();
51         }
52 
53         /// <summary>
54         /// The directory used for diagnostic log files.
55         /// </summary>
56         internal static string DebugDumpPath => s_debugDumpPath;
57 
58 #if !BUILDINGAPPXTASKS
59         /// <summary>
60         /// The filename that exceptions will be dumped to
61         /// </summary>
62         private static string s_dumpFileName;
63 #endif
64         /// <summary>
65         /// If the given exception is "ignorable under some circumstances" return false.
66         /// Otherwise it's "really bad", and return true.
67         /// This makes it possible to catch(Exception ex) without catching disasters.
68         /// </summary>
69         /// <param name="e"> The exception to check. </param>
70         /// <returns> True if exception is critical. </returns>
IsCriticalException(Exception e)71         internal static bool IsCriticalException(Exception e)
72         {
73             if (e is OutOfMemoryException
74 #if FEATURE_VARIOUS_EXCEPTIONS
75              || e is StackOverflowException
76              || e is ThreadAbortException
77              || e is ThreadInterruptedException
78              || e is AccessViolationException
79 #endif
80 #if !BUILDINGAPPXTASKS
81              || e is InternalErrorException
82 #endif
83              )
84             {
85                 // Ideally we would include NullReferenceException, because it should only ever be thrown by CLR (use ArgumentNullException for arguments)
86                 // but we should handle it if tasks and loggers throw it.
87 
88                 // ExecutionEngineException has been deprecated by the CLR
89                 return true;
90             }
91 
92 #if !CLR2COMPATIBILITY
93             // Check if any critical exceptions
94             var aggregateException = e as AggregateException;
95 
96             if (aggregateException != null)
97             {
98                 // If the aggregate exception contains a critical exception it is considered a critical exception
99                 if (aggregateException.InnerExceptions.Any(innerException => IsCriticalException(innerException)))
100                 {
101                     return true;
102                 }
103             }
104 #endif
105 
106             return false;
107         }
108 
109         /// <summary>
110         /// If the given exception is file IO related or expected return false.
111         /// Otherwise, return true.
112         /// </summary>
113         /// <param name="e">The exception to check.</param>
114         /// <returns>True if exception is not IO related or expected otherwise false.</returns>
NotExpectedException(Exception e)115         internal static bool NotExpectedException(Exception e)
116         {
117             return !IsIoRelatedException(e);
118         }
119 
120         /// <summary>
121         /// Determine whether the exception is file-IO related.
122         /// </summary>
123         /// <param name="e">The exception to check.</param>
124         /// <returns>True if exception is IO related.</returns>
IsIoRelatedException(Exception e)125         internal static bool IsIoRelatedException(Exception e)
126         {
127             // These all derive from IOException
128             //     DirectoryNotFoundException
129             //     DriveNotFoundException
130             //     EndOfStreamException
131             //     FileLoadException
132             //     FileNotFoundException
133             //     PathTooLongException
134             //     PipeException
135             return e is UnauthorizedAccessException
136                    || e is NotSupportedException
137                    || (e is ArgumentException && !(e is ArgumentNullException))
138                    || e is SecurityException
139                    || e is IOException;
140         }
141 
142         /// <summary> Checks if the exception is an XML one. </summary>
143         /// <param name="e"> Exception to check. </param>
144         /// <returns> True if exception is related to XML parsing. </returns>
IsXmlException(Exception e)145         internal static bool IsXmlException(Exception e)
146         {
147             return e is XmlException
148 #if FEATURE_VARIOUS_EXCEPTIONS
149                 || e is XmlSyntaxException
150                 || e is XmlSchemaException
151 #endif
152                 || e is UriFormatException; // XmlTextReader for example uses this under the covers
153         }
154 
155         /// <summary> Extracts line and column numbers from the exception if it is XML-related one. </summary>
156         /// <param name="e"> XML-related exception. </param>
157         /// <returns> Line and column numbers if available, (0,0) if not. </returns>
158         /// <remarks> This function works around the fact that XmlException and XmlSchemaException are not directly related. </remarks>
GetXmlLineAndColumn(Exception e)159         internal static LineAndColumn GetXmlLineAndColumn(Exception e)
160         {
161             var line = 0;
162             var column = 0;
163 
164             var xmlException = e as XmlException;
165             if (xmlException != null)
166             {
167                 line = xmlException.LineNumber;
168                 column = xmlException.LinePosition;
169             }
170             else
171             {
172 #if FEATURE_VARIOUS_EXCEPTIONS
173                 var schemaException = e as XmlSchemaException;
174                 if (schemaException != null)
175                 {
176                     line = schemaException.LineNumber;
177                     column = schemaException.LinePosition;
178                 }
179 #endif
180             }
181 
182             return new LineAndColumn
183             {
184                 Line = line,
185                 Column = column
186             };
187         }
188 
189 #if !BUILDINGAPPXTASKS
190 
191         /// <summary>
192         /// If the given exception is file IO related or Xml related return false.
193         /// Otherwise, return true.
194         /// </summary>
195         /// <param name="e">The exception to check.</param>
NotExpectedIoOrXmlException(Exception e)196         internal static bool NotExpectedIoOrXmlException(Exception e)
197         {
198             if
199             (
200                 IsXmlException(e)
201                 || !NotExpectedException(e)
202             )
203             {
204                 return false;
205             }
206 
207             return true;
208         }
209 
210         /// <summary>
211         /// If the given exception is reflection-related return false.
212         /// Otherwise, return true.
213         /// </summary>
214         /// <param name="e">The exception to check.</param>
NotExpectedReflectionException(Exception e)215         internal static bool NotExpectedReflectionException(Exception e)
216         {
217             // We are explicitly not handling TargetInvocationException. Those are just wrappers around
218             // exceptions thrown by the called code (such as a task or logger) which callers will typically
219             // want to treat differently.
220             if
221             (
222                 e is TypeLoadException                  // thrown when the common language runtime cannot find the assembly, the type within the assembly, or cannot load the type
223                 || e is MethodAccessException           // thrown when a class member is not found or access to the member is not permitted
224                 || e is MissingMethodException          // thrown when code in a dependent assembly attempts to access a missing method in an assembly that was modified
225                 || e is MemberAccessException           // thrown when a class member is not found or access to the member is not permitted
226                 || e is BadImageFormatException         // thrown when the file image of a DLL or an executable program is invalid
227                 || e is ReflectionTypeLoadException     // thrown by the Module.GetTypes method if any of the classes in a module cannot be loaded
228                 || e is TargetParameterCountException   // thrown when the number of parameters for an invocation does not match the number expected
229                 || e is InvalidCastException
230                 || e is AmbiguousMatchException         // thrown when binding to a member results in more than one member matching the binding criteria
231 #if FEATURE_VARIOUS_EXCEPTIONS
232                 || e is CustomAttributeFormatException  // thrown if a custom attribute on a data type is formatted incorrectly
233                 || e is InvalidFilterCriteriaException  // thrown in FindMembers when the filter criteria is not valid for the type of filter you are using
234                 || e is TargetException                 // thrown when an attempt is made to invoke a non-static method on a null object.  This may occur because the caller does not
235                                                         //     have access to the member, or because the target does not define the member, and so on.
236 #endif
237                 || e is MissingFieldException           // thrown when code in a dependent assembly attempts to access a missing field in an assembly that was modified.
238                 || !NotExpectedException(e)             // Reflection can throw IO exceptions if the assembly cannot be opened
239 
240             )
241             {
242                 return false;
243             }
244 
245             return true;
246         }
247 
248         /// <summary>
249         /// Serialization has been observed to throw TypeLoadException as
250         /// well as SerializationException and IO exceptions. (Obviously
251         /// it has to do reflection but it ought to be wrapping the exceptions.)
252         /// </summary>
NotExpectedSerializationException(Exception e)253         internal static bool NotExpectedSerializationException(Exception e)
254         {
255             if
256             (
257 #if FEATURE_VARIOUS_EXCEPTIONS
258                 e is SerializationException ||
259 #endif
260                 !NotExpectedReflectionException(e)
261             )
262             {
263                 return false;
264             }
265 
266             return true;
267         }
268 
269         /// <summary>
270         /// Returns false if this is a known exception thrown by the registry API.
271         /// </summary>
NotExpectedRegistryException(Exception e)272         internal static bool NotExpectedRegistryException(Exception e)
273         {
274             if (e is SecurityException
275              || e is UnauthorizedAccessException
276              || e is IOException
277              || e is ObjectDisposedException
278              || e is ArgumentException)
279             {
280                 return false;
281             }
282 
283             return true;
284         }
285 
286         /// <summary>
287         /// Returns false if this is a known exception thrown by function evaluation
288         /// </summary>
NotExpectedFunctionException(Exception e)289         internal static bool NotExpectedFunctionException(Exception e)
290         {
291             if (e is InvalidCastException
292              || e is ArgumentNullException
293              || e is FormatException
294              || e is InvalidOperationException
295              || !NotExpectedReflectionException(e))
296             {
297                 return false;
298             }
299 
300             return true;
301         }
302 
303 #if FEATURE_APPDOMAIN_UNHANDLED_EXCEPTION
304         /// <summary>
305         /// Dump any unhandled exceptions to a file so they can be diagnosed
306         /// </summary>
307         [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "It is called by the CLR")]
UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)308         internal static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
309         {
310             Exception ex = (Exception)e.ExceptionObject;
311             DumpExceptionToFile(ex);
312         }
313 #endif
314 
315         /// <summary>
316         /// Dump the exception information to a file
317         /// </summary>
DumpExceptionToFile(Exception ex)318         internal static void DumpExceptionToFile(Exception ex)
319         {
320             //  Locking on a type is not recommended.  However, we are doing it here to be extra cautious about compatibility because
321             //  this method previously had a [MethodImpl(MethodImplOptions.Synchronized)] attribute, which does lock on the type when
322             //  applied to a static method.
323             lock (typeof(ExceptionHandling))
324             {
325                 if (s_dumpFileName == null)
326                 {
327                     Guid guid = Guid.NewGuid();
328 
329                     // For some reason we get Watson buckets because GetTempPath gives us a folder here that doesn't exist.
330                     // Either because %TMP% is misdefined, or because they deleted the temp folder during the build.
331                     if (!Directory.Exists(DebugDumpPath))
332                     {
333                         // If this throws, no sense catching it, we can't log it now, and we're here
334                         // because we're a child node with no console to log to, so die
335                         Directory.CreateDirectory(DebugDumpPath);
336                     }
337 
338                     var pid = Process.GetCurrentProcess().Id;
339                     s_dumpFileName = Path.Combine(DebugDumpPath, $"MSBuild_pid-{pid}_{guid:n}.failure.txt");
340 
341                     using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true))
342                     {
343                         writer.WriteLine("UNHANDLED EXCEPTIONS FROM PROCESS {0}:", pid);
344                         writer.WriteLine("=====================");
345                     }
346                 }
347 
348                 using (StreamWriter writer = FileUtilities.OpenWrite(s_dumpFileName, append: true))
349                 {
350                     // "G" format is, e.g., 6/15/2008 9:15:07 PM
351                     writer.WriteLine(DateTime.Now.ToString("G", CultureInfo.CurrentCulture));
352                     writer.WriteLine(ex.ToString());
353                     writer.WriteLine("===================");
354                 }
355             }
356         }
357 #endif
358 
359         /// <summary> Line and column pair. </summary>
360         internal struct LineAndColumn
361         {
362             /// <summary> Gets or sets line number. </summary>
363             internal int Line { get; set; }
364 
365             /// <summary> Gets or sets column position. </summary>
366             internal int Column { get; set; }
367         }
368     }
369 }
370