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