// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Diagnostics; using System.Runtime.Serialization; namespace Microsoft.Build.Shared { /// /// This exception is to be thrown whenever an assumption we have made in the code turns out to be false. Thus, if this /// exception ever gets thrown, it is because of a bug in our own code, not because of something the user or project author /// did wrong. /// /// !~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~ /// WARNING: When this file is shared into multiple assemblies each assembly will view this as a different type. /// Don't throw this exception from one assembly and catch it in another. /// !~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~!~ /// /// [Serializable] internal sealed class InternalErrorException : Exception { /// /// Default constructor. /// SHOULD ONLY BE CALLED BY DESERIALIZER. /// SUPPLY A MESSAGE INSTEAD. /// internal InternalErrorException() : base() { // do nothing } /// /// Creates an instance of this exception using the given message. /// internal InternalErrorException ( String message ) : base("MSB0001: Internal MSBuild Error: " + message) { ConsiderDebuggerLaunch(message, null); } /// /// Creates an instance of this exception using the given message and inner exception. /// Adds the inner exception's details to the exception message because most bug reporters don't bother /// to provide the inner exception details which is typically what we care about. /// internal InternalErrorException ( String message, Exception innerException ) : base("MSB0001: Internal MSBuild Error: " + message + (innerException == null ? String.Empty : ("\n=============\n" + innerException.ToString() + "\n\n")), innerException) { ConsiderDebuggerLaunch(message, innerException); } #region Serialization (update when adding new class members) /// /// Private constructor used for (de)serialization. The constructor is private as this class is sealed /// If we ever add new members to this class, we'll need to update this. /// private InternalErrorException(SerializationInfo info, StreamingContext context) : base(info, context) { // Do nothing: no fields } // Base implementation of GetObjectData() is sufficient; we have no fields #endregion #region ConsiderDebuggerLaunch /// /// A fatal internal error due to a bug has occurred. Give the dev a chance to debug it, if possible. /// /// Will in all cases launch the debugger, if the environment variable "MSBUILDLAUNCHDEBUGGER" is set. /// /// In DEBUG build, will always launch the debugger, unless we are in razzle (_NTROOT is set) or in NUnit, /// or MSBUILDDONOTLAUNCHDEBUGGER is set (that could be useful in suite runs). /// We don't launch in retail or LKG so builds don't jam; they get a callstack, and continue or send a mail, etc. /// We don't launch in NUnit as tests often intentionally cause InternalErrorExceptions. /// /// Because we only call this method from this class, just before throwing an InternalErrorException, there is /// no danger that this suppression will cause a bug to only manifest itself outside NUnit /// (which would be most unfortunate!). Do not make this non-private. /// /// Unfortunately NUnit can't handle unhandled exceptions like InternalErrorException on anything other than /// the main test thread. However, there's still a callstack displayed before it quits. /// /// If it is going to launch the debugger, it first does a Debug.Fail to give information about what needs to /// be debugged -- the exception hasn't been thrown yet. This automatically displays the current callstack. /// private static void ConsiderDebuggerLaunch(string message, Exception innerException) { string innerMessage = (innerException == null) ? String.Empty : innerException.ToString(); if (Environment.GetEnvironmentVariable("MSBUILDLAUNCHDEBUGGER") != null) { LaunchDebugger(message, innerMessage); return; } #if DEBUG if (!RunningTests() && Environment.GetEnvironmentVariable("MSBUILDDONOTLAUNCHDEBUGGER") == null && Environment.GetEnvironmentVariable("_NTROOT") == null) { LaunchDebugger(message, innerMessage); return; } #endif } private static void LaunchDebugger(string message, string innerMessage) { #if FEATURE_DEBUG_LAUNCH Debug.Fail(message, innerMessage); Debugger.Launch(); #else Console.WriteLine("MSBuild Failure: " + message); if (!string.IsNullOrEmpty(innerMessage)) { Console.WriteLine(innerMessage); } Console.WriteLine("Waiting for debugger to attach to process: " + Process.GetCurrentProcess().Id); while (!Debugger.IsAttached) { System.Threading.Thread.Sleep(100); } #endif } #endregion #if EPIC_GAMES_REMOVED private static bool RunningTests() => BuildEnvironmentHelper.Instance.RunningTests; #else private static bool RunningTests() => false; #endif } }