Add support to ActionPreference.Break to break into debugger (#8205)

This commit is contained in:
Kirk Munro 2019-08-23 14:34:10 -03:00 committed by Dongbo Wang
parent 27fcb35913
commit 8b9f4124ce
16 changed files with 655 additions and 115 deletions

View file

@ -351,7 +351,7 @@ namespace Microsoft.PowerShell.Commands
}
/// <summary>
/// The optional breakpoint objects to use for debugging.
/// Gets or sets the optional breakpoint objects to use for debugging.
/// </summary>
[Experimental("Microsoft.PowerShell.Utility.PSDebugRunspaceWithBreakpoints", ExperimentAction.Show)]
[Parameter(Position = 1,
@ -555,19 +555,11 @@ namespace Microsoft.PowerShell.Commands
{
Runspace currentRunspace = this.Context.CurrentRunspace;
if ((currentRunspace != null) && (currentRunspace.Debugger != null))
if (currentRunspace != null && currentRunspace.Debugger != null)
{
if (!currentRunspace.Debugger.IsDebugHandlerSubscribed &&
(currentRunspace.Debugger.UnhandledBreakpointMode == UnhandledBreakpointProcessingMode.Ignore))
{
// No debugger attached and runspace debugging is not enabled. Enable runspace debugging here
// so that this command is effective.
currentRunspace.Debugger.UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait;
}
// Set debugger to step mode so that a break occurs immediately.
currentRunspace.Debugger.SetDebuggerStepMode(true);
WriteVerbose(string.Format(CultureInfo.InvariantCulture, Debugger.DebugBreakMessage, MyInvocation.ScriptLineNumber, MyInvocation.ScriptName));
currentRunspace.Debugger.Break();
}
}

View file

@ -277,18 +277,26 @@ namespace System.Management.Automation
public enum ActionPreference
{
/// <summary>Ignore this event and continue</summary>
SilentlyContinue,
SilentlyContinue = 0,
/// <summary>Stop the command</summary>
Stop,
Stop = 1,
/// <summary>Handle this event as normal and continue</summary>
Continue,
Continue = 2,
/// <summary>Ask whether to stop or continue</summary>
Inquire,
Inquire = 3,
/// <summary>Ignore the event completely (not even logging it to the target stream)</summary>
Ignore,
Ignore = 4,
/// <summary>Suspend the command for further diagnosis. Supported only for workflows.</summary>
Suspend,
}
Suspend = 5,
/// <summary>Enter the debugger.</summary>
Break = 6,
} // enum ActionPreference
#endregion ActionPreference
#region ConfirmImpact

View file

@ -570,9 +570,7 @@ namespace System.Management.Automation
internal T GetEnumPreference<T>(VariablePath preferenceVariablePath, T defaultPref, out bool defaultUsed)
{
CmdletProviderContext context = null;
SessionStateScope scope = null;
object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out context, out scope);
object val = EngineSessionState.GetVariableValue(preferenceVariablePath, out _, out _);
if (val is T)
{
// We don't want to support "Ignore" as action preferences, as it leads to bad
@ -1048,7 +1046,7 @@ namespace System.Management.Automation
get
{
bool defaultUsed = false;
return this.GetEnumPreference<ActionPreference>(
return this.GetEnumPreference(
SpecialVariables.DebugPreferenceVarPath,
InitialSessionState.defaultDebugPreference,
out defaultUsed);
@ -1069,7 +1067,7 @@ namespace System.Management.Automation
get
{
bool defaultUsed = false;
return this.GetEnumPreference<ActionPreference>(
return this.GetEnumPreference(
SpecialVariables.VerbosePreferenceVarPath,
InitialSessionState.defaultVerbosePreference,
out defaultUsed);
@ -1090,7 +1088,7 @@ namespace System.Management.Automation
get
{
bool defaultUsed = false;
return this.GetEnumPreference<ActionPreference>(
return this.GetEnumPreference(
SpecialVariables.ErrorActionPreferenceVarPath,
InitialSessionState.defaultErrorActionPreference,
out defaultUsed);
@ -1111,7 +1109,7 @@ namespace System.Management.Automation
get
{
bool defaultUsed = false;
return this.GetEnumPreference<ActionPreference>(
return this.GetEnumPreference(
SpecialVariables.WarningPreferenceVarPath,
InitialSessionState.defaultWarningPreference,
out defaultUsed);
@ -1132,7 +1130,7 @@ namespace System.Management.Automation
get
{
bool defaultUsed = false;
return this.GetEnumPreference<ActionPreference>(
return this.GetEnumPreference(
SpecialVariables.InformationPreferenceVarPath,
InitialSessionState.defaultInformationPreference,
out defaultUsed);
@ -1178,7 +1176,7 @@ namespace System.Management.Automation
get
{
bool defaultUsed = false;
return this.GetEnumPreference<ConfirmImpact>(
return this.GetEnumPreference(
SpecialVariables.ConfirmPreferenceVarPath,
InitialSessionState.defaultConfirmPreference,
out defaultUsed);

View file

@ -5,13 +5,12 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Security;
using System.Management.Automation.Host;
using System.Management.Automation.Internal.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Internal.Host;
using System.Management.Automation.Remoting;
using System.Management.Automation.Runspaces;
using System.Threading;
using Dbg = System.Management.Automation.Diagnostics;
@ -269,6 +268,12 @@ namespace System.Management.Automation
#endif
}
/// <summary>
/// Writes an object enumerated from a collection to the output pipe.
/// </summary>
/// <param name="sendToPipeline">
/// The enumerated object that needs to be written to the pipeline.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
@ -276,7 +281,7 @@ namespace System.Management.Automation
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread
/// Not permitted at this time or from this thread.
/// </exception>
private void DoWriteEnumeratedObject(object sendToPipeline)
{
@ -411,6 +416,12 @@ namespace System.Management.Automation
if (WriteHelper_ShouldWrite(
preference, lastProgressContinueStatus))
{
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(progressRecord);
}
ui.WriteProgress(sourceId, progressRecord);
}
@ -476,6 +487,12 @@ namespace System.Management.Automation
record.SetInvocationInfo(MyInvocation);
}
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(record);
}
if (DebugOutputPipe != null)
{
if (CBhost != null && CBhost.InternalUI != null &&
@ -564,6 +581,12 @@ namespace System.Management.Automation
record.SetInvocationInfo(MyInvocation);
}
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(record);
}
if (VerboseOutputPipe != null)
{
if (CBhost != null && CBhost.InternalUI != null &&
@ -652,6 +675,12 @@ namespace System.Management.Automation
record.SetInvocationInfo(MyInvocation);
}
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(record);
}
if (WarningOutputPipe != null)
{
if (CBhost != null && CBhost.InternalUI != null &&
@ -712,6 +741,12 @@ namespace System.Management.Automation
if (overrideInquire && preference == ActionPreference.Inquire)
preference = ActionPreference.Continue;
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(record);
}
if (preference != ActionPreference.Ignore)
{
if (InformationOutputPipe != null)
@ -2051,6 +2086,14 @@ namespace System.Management.Automation
CmdletInvocationException e =
new CmdletInvocationException(errorRecord);
// If the error action preference is set to break, break immediately
// into the debugger
if (ErrorAction == ActionPreference.Break)
{
Context.Debugger?.Break(e.InnerException ?? e);
}
// Code sees only that execution stopped
throw ManageException(e);
}
@ -2551,8 +2594,12 @@ namespace System.Management.Automation
#region Write
internal bool UseSecurityContextRun = true;
// NOTICE-2004/06/08-JonN 959638
// Use this variant to skip the ThrowIfWriteNotPermitted check
/// <summary>
/// Writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check.
/// </summary>
/// <param name="sendToPipeline">
/// The object to write to the output pipe.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
@ -2571,8 +2618,12 @@ namespace System.Management.Automation
this.OutputPipe.Add(sendToPipeline);
}
// NOTICE-2004/06/08-JonN 959638
// Use this variant to skip the ThrowIfWriteNotPermitted check
/// <summary>
/// Enumerates and writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check.
/// </summary>
/// <param name="sendToPipeline">
/// The object to enumerate and write to the output pipe.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
@ -2594,7 +2645,9 @@ namespace System.Management.Automation
foreach (object toConvert in enumerable)
{
if (AutomationNull.Value == toConvert)
{
continue;
}
object converted = LanguagePrimitives.AsPSObjectOrNull(toConvert);
convertedList.Add(converted);
@ -2660,6 +2713,12 @@ namespace System.Management.Automation
preference = ActionPreference.Continue;
}
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(errorRecord);
}
#if CORECLR
// SecurityContext is not supported in CoreCLR
DoWriteError(new KeyValuePair<ErrorRecord, ActionPreference>(errorRecord, preference));
@ -2898,7 +2957,7 @@ namespace System.Management.Automation
if (!_isConfirmPreferenceCached)
{
bool defaultUsed = false;
_confirmPreference = Context.GetEnumPreference<ConfirmImpact>(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed);
_confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed);
_isConfirmPreferenceCached = true;
}
@ -2933,7 +2992,7 @@ namespace System.Management.Automation
{
bool defaultUsed = false;
_debugPreference = Context.GetEnumPreference<ActionPreference>(SpecialVariables.DebugPreferenceVarPath, _debugPreference, out defaultUsed);
_debugPreference = Context.GetEnumPreference(SpecialVariables.DebugPreferenceVarPath, _debugPreference, out defaultUsed);
// If the host couldn't prompt for the debug action anyways, change it to 'Continue'.
// This lets hosts still see debug output without having to implement the prompting logic.
@ -2992,7 +3051,7 @@ namespace System.Management.Automation
if (!_isVerbosePreferenceCached)
{
bool defaultUsed = false;
_verbosePreference = Context.GetEnumPreference<ActionPreference>(
_verbosePreference = Context.GetEnumPreference(
SpecialVariables.VerbosePreferenceVarPath,
_verbosePreference,
out defaultUsed);
@ -3029,7 +3088,7 @@ namespace System.Management.Automation
if (!_isWarningPreferenceCached)
{
bool defaultUsed = false;
_warningPreference = Context.GetEnumPreference<ActionPreference>(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed);
_warningPreference = Context.GetEnumPreference(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed);
}
return _warningPreference;
@ -3198,7 +3257,7 @@ namespace System.Management.Automation
if (!_isErrorActionPreferenceCached)
{
bool defaultUsed = false;
_errorAction = Context.GetEnumPreference<ActionPreference>(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed);
_errorAction = Context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed);
_isErrorActionPreferenceCached = true;
}
@ -3233,7 +3292,7 @@ namespace System.Management.Automation
if (!_isProgressPreferenceCached)
{
bool defaultUsed = false;
_progressPreference = Context.GetEnumPreference<ActionPreference>(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed);
_progressPreference = Context.GetEnumPreference(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed);
_isProgressPreferenceCached = true;
}
@ -3265,7 +3324,7 @@ namespace System.Management.Automation
if (!_isInformationPreferenceCached)
{
bool defaultUsed = false;
_informationPreference = Context.GetEnumPreference<ActionPreference>(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed);
_informationPreference = Context.GetEnumPreference(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed);
_isInformationPreferenceCached = true;
}
@ -3364,6 +3423,7 @@ namespace System.Management.Automation
case ActionPreference.Continue:
case ActionPreference.Stop:
case ActionPreference.Inquire:
case ActionPreference.Break:
return true;
default:
@ -3416,6 +3476,7 @@ namespace System.Management.Automation
case ActionPreference.Ignore: // YesToAll
case ActionPreference.SilentlyContinue:
case ActionPreference.Continue:
case ActionPreference.Break:
return ContinueStatus.Yes;
case ActionPreference.Stop:

View file

@ -666,6 +666,15 @@ namespace System.Management.Automation
#region Internal Methods
/// <summary>
/// Breaks into the debugger.
/// </summary>
/// <param name="triggerObject">The object that triggered the breakpoint, if there is one.</param>
internal virtual void Break(object triggerObject = null)
{
throw new PSNotImplementedException();
}
/// <summary>
/// Passes the debugger command to the internal script debugger command processor. This
/// is used internally to handle debugger commands such as list, help, etc.
@ -947,6 +956,11 @@ namespace System.Management.Automation
}
}
/// <summary>
/// Gets or sets the object that triggered the current breakpoint.
/// </summary>
private object TriggerObject { get; set; }
#endregion properties
#region internal methods
@ -1734,7 +1748,9 @@ namespace System.Management.Automation
return;
}
_context.SetVariable(SpecialVariables.PSDebugContextVarPath, new PSDebugContext(invocationInfo, breakpoints));
bool oldQuestionMarkVariableValue = _context.QuestionMarkVariableValue;
_context.SetVariable(SpecialVariables.PSDebugContextVarPath, new PSDebugContext(invocationInfo, breakpoints, TriggerObject));
FunctionInfo defaultPromptInfo = null;
string originalPromptString = null;
@ -1848,6 +1864,8 @@ namespace System.Management.Automation
DebuggerStopEventArgs oldArgs;
_debuggerStopEventArgs.TryPop(out oldArgs);
_context.QuestionMarkVariableValue = oldQuestionMarkVariableValue;
_inBreakpoint = false;
}
}
@ -2500,6 +2518,39 @@ namespace System.Management.Automation
}
}
/// <summary>
/// Breaks into the debugger.
/// </summary>
/// <param name="triggerObject">The object that triggered the breakpoint, if there is one.</param>
internal override void Break(object triggerObject = null)
{
if (!IsDebugHandlerSubscribed &&
(UnhandledBreakpointMode == UnhandledBreakpointProcessingMode.Ignore))
{
// No debugger attached and runspace debugging is not enabled. Enable runspace debugging here
// so that this command is effective.
UnhandledBreakpointMode = UnhandledBreakpointProcessingMode.Wait;
}
// Store the triggerObject so that we can add it to PSDebugContext
TriggerObject = triggerObject;
// Set debugger to step mode so that a break can occur.
SetDebuggerStepMode(true);
// If the debugger is enabled and we are not in a breakpoint, trigger an immediate break in the current location
if (_context._debuggingMode > 0)
{
using (IEnumerator<CallStackFrame> enumerator = GetCallStack().GetEnumerator())
{
if (enumerator.MoveNext())
{
OnSequencePointHit(enumerator.Current.FunctionContext);
}
}
}
}
/// <summary>
/// Passes the debugger command to the internal script debugger command processor. This
/// is used internally to handle debugger commands such as list, help, etc.
@ -4101,6 +4152,15 @@ namespace System.Management.Automation
get { return _wrappedDebugger.IsActive; }
}
/// <summary>
/// Breaks into the debugger.
/// </summary>
/// <param name="triggerObject">The object that triggered the breakpoint, if there is one.</param>
internal override void Break(object triggerObject = null)
{
_wrappedDebugger.Break(triggerObject);
}
#endregion
#region IDisposable
@ -4776,15 +4836,15 @@ namespace System.Management.Automation
public DebuggerCommandProcessor()
{
_commandTable = new Dictionary<string, DebuggerCommand>(StringComparer.OrdinalIgnoreCase);
_commandTable[StepCommand] = _commandTable[StepShortcut] = new DebuggerCommand(StepCommand, DebuggerResumeAction.StepInto, true, false);
_commandTable[StepOutCommand] = _commandTable[StepOutShortcut] = new DebuggerCommand(StepOutCommand, DebuggerResumeAction.StepOut, false, false);
_commandTable[StepOverCommand] = _commandTable[StepOverShortcut] = new DebuggerCommand(StepOverCommand, DebuggerResumeAction.StepOver, true, false);
_commandTable[ContinueCommand] = _commandTable[ContinueShortcut] = new DebuggerCommand(ContinueCommand, DebuggerResumeAction.Continue, false, false);
_commandTable[StopCommand] = _commandTable[StopShortcut] = new DebuggerCommand(StopCommand, DebuggerResumeAction.Stop, false, false);
_commandTable[GetStackTraceShortcut] = new DebuggerCommand("get-pscallstack", null, false, false);
_commandTable[HelpCommand] = _commandTable[HelpShortcut] = _helpCommand = new DebuggerCommand(HelpCommand, null, false, true);
_commandTable[ListCommand] = _commandTable[ListShortcut] = _listCommand = new DebuggerCommand(ListCommand, null, true, true);
_commandTable[string.Empty] = new DebuggerCommand(string.Empty, null, false, true);
_commandTable[StepCommand] = _commandTable[StepShortcut] = new DebuggerCommand(StepCommand, DebuggerResumeAction.StepInto, repeatOnEnter: true, executedByDebugger: false);
_commandTable[StepOutCommand] = _commandTable[StepOutShortcut] = new DebuggerCommand(StepOutCommand, DebuggerResumeAction.StepOut, repeatOnEnter: false, executedByDebugger: false);
_commandTable[StepOverCommand] = _commandTable[StepOverShortcut] = new DebuggerCommand(StepOverCommand, DebuggerResumeAction.StepOver, repeatOnEnter: true, executedByDebugger: false);
_commandTable[ContinueCommand] = _commandTable[ContinueShortcut] = new DebuggerCommand(ContinueCommand, DebuggerResumeAction.Continue, repeatOnEnter: false, executedByDebugger: false);
_commandTable[StopCommand] = _commandTable[StopShortcut] = new DebuggerCommand(StopCommand, DebuggerResumeAction.Stop, repeatOnEnter: false, executedByDebugger: false);
_commandTable[GetStackTraceShortcut] = new DebuggerCommand("get-pscallstack", null, repeatOnEnter: false, executedByDebugger: false);
_commandTable[HelpCommand] = _commandTable[HelpShortcut] = _helpCommand = new DebuggerCommand(HelpCommand, null, repeatOnEnter: false, executedByDebugger: true);
_commandTable[ListCommand] = _commandTable[ListShortcut] = _listCommand = new DebuggerCommand(ListCommand, null, repeatOnEnter: true, executedByDebugger: true);
_commandTable[string.Empty] = new DebuggerCommand(string.Empty, null, repeatOnEnter: false, executedByDebugger: true);
}
/// <summary>
@ -5129,11 +5189,22 @@ namespace System.Management.Automation
public class PSDebugContext
{
/// <summary>
/// Constructor.
/// Initializes a new instance of the <see cref="PSDebugContext"/> class.
/// </summary>
/// <param name="invocationInfo">InvocationInfo.</param>
/// <param name="breakpoints">Breakpoints.</param>
/// <param name="invocationInfo">The invocation information for the current command.</param>
/// <param name="breakpoints">The breakpoint(s) that caused the script to break in the debugger.</param>
public PSDebugContext(InvocationInfo invocationInfo, List<Breakpoint> breakpoints)
: this(invocationInfo, breakpoints, triggerObject: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PSDebugContext"/> class.
/// </summary>
/// <param name="invocationInfo">The invocation information for the current command.</param>
/// <param name="breakpoints">The breakpoint(s) that caused the script to break in the debugger.</param>
/// <param name="triggerObject">The object that caused the script to break in the debugger.</param>
public PSDebugContext(InvocationInfo invocationInfo, List<Breakpoint> breakpoints, object triggerObject)
{
if (breakpoints == null)
{
@ -5142,6 +5213,7 @@ namespace System.Management.Automation
this.InvocationInfo = invocationInfo;
this.Breakpoints = breakpoints.ToArray();
this.Trigger = triggerObject;
}
/// <summary>
@ -5154,6 +5226,11 @@ namespace System.Management.Automation
/// were hit. Otherwise, the execution was suspended as part of a step operation.
/// </summary>
public Breakpoint[] Breakpoints { get; private set; }
/// <summary>
/// Gets the object that triggered the current dynamic breakpoint.
/// </summary>
public object Trigger { get; private set; }
}
#endregion

View file

@ -379,6 +379,7 @@ namespace System.Management.Automation.Internal.Host
switch (preference)
{
case ActionPreference.Continue:
case ActionPreference.Break:
WriteDebugLineHelper(message);
break;
case ActionPreference.SilentlyContinue:

View file

@ -4954,7 +4954,8 @@ namespace System.Management.Automation.Language
UpdatePosition(throwStatementAst),
Expression.Throw(Expression.Call(CachedReflectionInfo.ExceptionHandlingOps_ConvertToException,
throwExpr.Convert(typeof(object)),
Expression.Constant(throwStatementAst.Extent))));
Expression.Constant(throwStatementAst.Extent),
Expression.Constant(throwStatementAst.IsRethrow))));
}
#endregion Statements

View file

@ -145,9 +145,9 @@ namespace System.Management.Automation.Language
if (!string.IsNullOrEmpty(sourceLine))
{
int spacesBeforeError = position.StartColumnNumber - 1;
int errorLength = (position.StartLineNumber == position.EndLineNumber)
int errorLength = (position.StartLineNumber == position.EndLineNumber && position.EndColumnNumber <= sourceLine.Length + 1)
? position.EndColumnNumber - position.StartColumnNumber
: sourceLine.TrimEnd().Length - position.StartColumnNumber + 1;
: sourceLine.Length - position.StartColumnNumber + 1;
// Expand tabs before figuring out if we need to truncate the line
if (sourceLine.IndexOf('\t') != -1)

View file

@ -1981,6 +1981,11 @@ namespace System.Management.Automation
return _wrappedDebugger.Value.GetCallStack();
}
internal override void Break(object triggerObject = null)
{
_wrappedDebugger.Value.Break(triggerObject);
}
#endregion
#region IDisposable

View file

@ -108,10 +108,13 @@ namespace System.Management.Automation
if (string.IsNullOrEmpty(commandName))
{
throw InterpreterError.NewInterpreterException(command, typeof(RuntimeException),
commandExtent, "BadExpression",
ParserStrings.BadExpression,
dotSource ? "." : "&");
throw InterpreterError.NewInterpreterException(
command,
typeof(RuntimeException),
commandExtent,
"BadExpression",
ParserStrings.BadExpression,
dotSource ? "." : "&");
}
try
@ -329,7 +332,10 @@ namespace System.Management.Automation
object parameterValue = de.Value;
string parameterText = GetParameterText(parameterName);
if (markUntrustedData) { ExecutionContext.MarkObjectAsUntrusted(parameterValue); }
if (markUntrustedData)
{
ExecutionContext.MarkObjectAsUntrusted(parameterValue);
}
yield return CommandParameterInternal.CreateParameterWithArgument(
splatAst, parameterName, parameterText,
@ -343,7 +349,10 @@ namespace System.Management.Automation
{
foreach (object obj in enumerableValue)
{
if (markUntrustedData) { ExecutionContext.MarkObjectAsUntrusted(obj); }
if (markUntrustedData)
{
ExecutionContext.MarkObjectAsUntrusted(obj);
}
yield return SplatEnumerableElement(obj, splatAst);
}
@ -1335,15 +1344,15 @@ namespace System.Management.Automation
}
// Add key and values from left hand side...
foreach (object myKey in lvalDict.Keys)
foreach (object key in lvalDict.Keys)
{
newDictionary.Add(myKey, lvalDict[myKey]);
newDictionary.Add(key, lvalDict[key]);
}
// and the right-hand side
foreach (object myKey in rvalDict.Keys)
foreach (object key in rvalDict.Keys)
{
newDictionary.Add(myKey, rvalDict[myKey]);
newDictionary.Add(key, rvalDict[key]);
}
return newDictionary;
@ -1595,25 +1604,38 @@ namespace System.Management.Automation
// set $? to false indicating an error
context.QuestionMarkVariableValue = false;
bool anyTrapHandlers = funcContext._traps.Any() && funcContext._traps.Last().Item2 != null;
ActionPreference preference = GetErrorActionPreference(context);
if (!anyTrapHandlers && !ExceptionHandlingOps.NeedToQueryForActionPreference(rte, context))
// If the exception was not rethrown and we are not currently
// handling an exception, then the exception is new, and we
// can break on it if requested.
if (!rte.WasRethrown &&
context.CurrentExceptionBeingHandled == null &&
preference == ActionPreference.Break)
{
context.Debugger?.Break(rte);
}
// Item2 in the trap tuples is the action (script) for the trap.
// A null action script is only used to indicate when exceptions
// should be thrown up to a higher level, and doesn't count as an
// actual trap handler in the function context.
bool anyTrapHandlers = funcContext._traps.Count > 0 && funcContext._traps[funcContext._traps.Count - 1].Item2 != null;
if (anyTrapHandlers)
{
// update the action preference according to how the exception is
// handled in the trap statement(s).
preference = ProcessTraps(funcContext, rte);
}
else if (ExceptionCannotBeStoppedContinuedOrIgnored(rte, context))
{
throw rte;
}
ActionPreference preference;
if (anyTrapHandlers)
else if (preference == ActionPreference.Inquire && !rte.SuppressPromptInInterpreter)
{
preference = ProcessTraps(funcContext, rte);
preference = InquireForActionPreference(rte.Message, context);
}
else
{
preference = ExceptionHandlingOps.QueryForAction(rte, rte.Message, context);
}
// set the value of $? here in case it is reset in trap handling.
context.QuestionMarkVariableValue = false;
if ((preference == ActionPreference.SilentlyContinue) ||
(preference == ActionPreference.Ignore))
@ -1637,12 +1659,7 @@ namespace System.Management.Automation
throw rte;
}
bool b = ExceptionHandlingOps.ReportErrorRecord(extent, rte, context);
// set the value of $? here in case it is reset in error reporting
context.QuestionMarkVariableValue = false;
if (!b)
if (!ReportErrorRecord(extent, rte, context))
{
throw rte;
}
@ -1680,10 +1697,12 @@ namespace System.Management.Automation
if (handler != -1)
{
Diagnostics.Assert(exception != null, "Exception object can't be null.");
var context = funcContext._executionContext;
try
{
ErrorRecord err = rte.ErrorRecord;
var context = funcContext._executionContext;
// CurrentCommandProcessor is normally not null, but it is null
// when executing some unit tests through reflection.
if (context.CurrentCommandProcessor != null)
@ -1747,13 +1766,37 @@ namespace System.Management.Automation
// Terminate this block of statements.
return ActionPreference.Stop;
}
finally
{
// The questionmark variable will always be false when we process a trap, so
// set it to false to ensure it didn't change as a result of anything done
// inside the trap
context.QuestionMarkVariableValue = false;
}
}
return ActionPreference.Stop;
}
/// <summary>
/// Determine if we should continue or not after and error or exception....
/// Gets the current error action preference value.
/// </summary>
/// <param name="context">The execution context.</param>
/// <returns>The preference the user selected.</returns>
/// <remarks>
/// Error action is decided by error action preference. If preference is inquire, we will
/// prompt user for their preference.
/// </remarks>
internal static ActionPreference GetErrorActionPreference(ExecutionContext context)
{
return context.GetEnumPreference(
SpecialVariables.ErrorActionPreferenceVarPath,
ActionPreference.Continue,
out _);
}
/// <summary>
/// Determine if we should continue or not after an error or exception.
/// </summary>
/// <param name="rte">The RuntimeException which was reported.</param>
/// <param name="message">The message to display.</param>
@ -1766,10 +1809,11 @@ namespace System.Management.Automation
internal static ActionPreference QueryForAction(RuntimeException rte, string message, ExecutionContext context)
{
// 906264 "$ErrorActionPreference="Inquire" prevents original non-terminating error from being reported to $error"
bool defaultUsed;
ActionPreference preference =
context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath,
ActionPreference.Continue, out defaultUsed);
context.GetEnumPreference(
SpecialVariables.ErrorActionPreferenceVarPath,
ActionPreference.Continue,
out _);
if (preference != ActionPreference.Inquire || rte.SuppressPromptInInterpreter)
return preference;
@ -1808,12 +1852,16 @@ namespace System.Management.Automation
string caption = ParserStrings.ExceptionActionPromptCaption;
bool oldQuestionMarkVariableValue = context.QuestionMarkVariableValue;
int choice;
while ((choice = ui.PromptForChoice(caption, message, choices, 0)) == 3)
{
context.EngineHostInterface.EnterNestedPrompt();
}
context.QuestionMarkVariableValue = oldQuestionMarkVariableValue;
if (choice == 0)
return ActionPreference.Continue;
@ -1863,13 +1911,13 @@ namespace System.Management.Automation
}
}
internal static bool NeedToQueryForActionPreference(RuntimeException rte, ExecutionContext context)
internal static bool ExceptionCannotBeStoppedContinuedOrIgnored(RuntimeException rte, ExecutionContext context)
{
return !context.PropagateExceptionsToEnclosingStatementBlock
&& context.ShellFunctionErrorOutputPipe != null
&& !context.CurrentPipelineStopping
&& !rte.SuppressPromptInInterpreter
&& !(rte is PipelineStoppedException);
return context.PropagateExceptionsToEnclosingStatementBlock
|| context.ShellFunctionErrorOutputPipe == null
|| context.CurrentPipelineStopping
|| rte.SuppressPromptInInterpreter
|| rte is PipelineStoppedException;
}
/// <summary>
@ -1901,10 +1949,13 @@ namespace System.Management.Automation
context.ShellFunctionErrorOutputPipe.Add(errorWrap);
// set the value of $? here in case it is reset in error reporting.
context.QuestionMarkVariableValue = false;
return true;
}
internal static RuntimeException ConvertToException(object result, IScriptExtent extent)
internal static RuntimeException ConvertToException(object result, IScriptExtent extent, bool rethrow)
{
result = PSObject.Base(result);
@ -1913,13 +1964,14 @@ namespace System.Management.Automation
{
InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent);
runtimeException.WasThrownFromThrowStatement = true;
runtimeException.WasRethrown = rethrow;
return runtimeException;
}
ErrorRecord er = result as ErrorRecord;
if (er != null)
{
runtimeException = new RuntimeException(er.ToString(), er.Exception, er) { WasThrownFromThrowStatement = true };
runtimeException = new RuntimeException(er.ToString(), er.Exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow };
InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent);
return runtimeException;
@ -1929,7 +1981,7 @@ namespace System.Management.Automation
if (exception != null)
{
er = new ErrorRecord(exception, exception.Message, ErrorCategory.OperationStopped, null);
runtimeException = new RuntimeException(exception.Message, exception, er) { WasThrownFromThrowStatement = true };
runtimeException = new RuntimeException(exception.Message, exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow };
InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent);
return runtimeException;
}
@ -1940,7 +1992,7 @@ namespace System.Management.Automation
exception = new RuntimeException(message, null);
er = new ErrorRecord(exception, message, ErrorCategory.OperationStopped, null);
runtimeException = new RuntimeException(message, exception, er) { WasThrownFromThrowStatement = true };
runtimeException = new RuntimeException(message, exception, er) { WasThrownFromThrowStatement = true, WasRethrown = rethrow };
runtimeException.SetTargetObject(result);
InterpreterError.UpdateExceptionErrorRecordPosition(runtimeException, extent);

View file

@ -284,6 +284,8 @@ namespace System.Management.Automation
private bool _thrownByThrowStatement;
internal bool WasRethrown { get; set; }
/// <summary>
/// Fix for BUG: Windows Out Of Band Releases: 906263 and 906264
/// The interpreter prompt CommandBaseStrings:InquireHalt

View file

@ -1,5 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Describe "Tests for (error, warning, etc) action preference" -Tags "CI" {
BeforeAll {
$orgin = $GLOBAL:errorActionPreference
@ -37,7 +38,7 @@ Describe "Tests for (error, warning, etc) action preference" -Tags "CI" {
$e = {
$GLOBAL:errorActionPreference = "Ignore"
Get-Process -Name asdfasdfasdf
} | Should -Throw -ErrorId 'System.NotSupportedException,Microsoft.PowerShell.Commands.GetProcessCommand' -PassThru
} | Should -Throw -ErrorId 'System.NotSupportedException' -PassThru
$e.CategoryInfo.Reason | Should -BeExactly 'NotSupportedException'
$GLOBAL:errorActionPreference = $orgin
@ -115,3 +116,240 @@ Describe "Tests for (error, warning, etc) action preference" -Tags "CI" {
Remove-Item "$testdrive\test.txt" -Force
}
}
Describe 'ActionPreference.Break tests' -tag 'CI' {
BeforeAll {
Register-DebuggerHandler
}
AfterAll {
Unregister-DebuggerHandler
}
Context '-ErrorAction Break should break on a non-terminating error' {
BeforeAll {
$testScript = {
function Test-Break {
[CmdletBinding()]
param()
try {
# Generate a non-terminating error
Write-Error 'This is a non-terminating error.'
# Do something afterwards
'This should still run'
} catch {
'Do nothing'
} finally {
'This finally runs'
}
}
Test-Break -ErrorAction Break
}
$results = @(Test-Debugger -ScriptBlock $testScript -CommandQueue 'v', 'v')
}
It 'Should show 3 debugger commands were invoked' {
# There is always an implicit 'c' command that keeps the debugger automation moving
$results.Count | Should -Be 3
}
It 'The breakpoint should be the statement that generated the non-terminating error' {
$results[0] | ShouldHaveExtent -Line 7 -FromColumn 25 -ToColumn 71
}
It 'The second statement should be the statement after that which generated the non-terminating error' {
$results[1] | ShouldHaveExtent -Line 9 -FromColumn 25 -ToColumn 48
}
It 'The third statement should be the statement in the finally block' {
$results[2] | ShouldHaveExtent -Line 13 -FromColumn 25 -ToColumn 44
}
}
Context '-ErrorAction Break should break on a terminating error' {
BeforeAll {
$testScript = {
function Test-Break {
[CmdletBinding()]
param()
try {
# Generate a terminating error
Get-Process -TheAnswer 42
# Do something afterwards
'This should not run'
} catch {
'Do nothing'
} finally {
'This finally runs'
}
}
Test-Break -ErrorAction Break
}
$results = @(Test-Debugger -ScriptBlock $testScript -CommandQueue 'v', 'v')
}
It 'Should show 3 debugger commands were invoked' {
# There is always an implicit 'c' command that keeps the debugger automation moving
$results.Count | Should -Be 3
}
It 'The breakpoint should be the statement that generated the terminating error' {
$results[0] | ShouldHaveExtent -Line 7 -FromColumn 25 -ToColumn 50
}
It 'The second statement should be the statement in the catch block where the terminating error is caught' {
$results[1] | ShouldHaveExtent -Line 11 -FromColumn 25 -ToColumn 37
}
It 'The third statement should be the statement in the finally block' {
$results[2] | ShouldHaveExtent -Line 13 -FromColumn 25 -ToColumn 44
}
}
Context '-ErrorAction Break should not break on a naked rethrow' {
BeforeAll {
$testScript = {
function Test-Break {
[CmdletBinding()]
param()
try {
try {
# Generate a terminating error
Get-Process -TheAnswer 42
} catch {
throw
}
} catch {
# Swallow the exception here
}
}
Test-Break -ErrorAction Break
}
$results = @(Test-Debugger -ScriptBlock $testScript)
}
It 'Should show 1 debugger command was invoked' {
# ErrorAction break should only trigger on the initial terminating error
$results.Count | Should -Be 1
}
It 'The breakpoint should be the statement that generated the terminating error' {
$results[0] | ShouldHaveExtent -Line 8 -FromColumn 29 -ToColumn 54
}
}
Context '-ErrorAction Break should break when throwing a specific error or object' {
BeforeAll {
$testScript = {
function Test-Break {
[CmdletBinding()]
param()
try {
try {
# Generate a terminating error
Get-Process -TheAnswer 42
} catch {
throw $_
}
} catch {
# Swallow the exception here
}
}
Test-Break -ErrorAction Break
}
$results = @(Test-Debugger -ScriptBlock $testScript)
}
It 'Should show 2 debugger commands were invoked' {
# ErrorAction break should trigger on the initial terminating error and the throw
# since it throws a "new" error (throwing anything is considered a new terminating
# error)
$results.Count | Should -Be 2
}
It 'The first breakpoint should be the statement that generated the terminating error' {
$results[0] | ShouldHaveExtent -Line 8 -FromColumn 29 -ToColumn 54
}
It 'The second breakpoint should be the statement that threw $_' {
$results[1] | ShouldHaveExtent -Line 10 -FromColumn 29 -ToColumn 37
}
}
Context 'Other message types should break on their corresponding messages when requested' {
BeforeAll {
$testScript = {
function Test-Break {
[CmdletBinding()]
param()
Write-Warning -Message 'This is a warning message'
Write-Verbose -Message 'This is a verbose message'
Write-Debug -Message 'This is a debug message'
Write-Information -MessageData 'This is an information message'
Write-Progress -Activity 'This shows progress'
}
Test-Break -WarningAction Break -InformationAction Break *>$null
$WarningPreference = $VerbosePreference = $DebugPreference = $InformationPreference = $ProgressPreference = [System.Management.Automation.ActionPreference]::Break
Test-Break *>$null
}
$results = @(Test-Debugger -ScriptBlock $testScript)
}
It 'Should show 7 debugger commands were invoked' {
# When no debugger commands are provided, 'c' is invoked every time a breakpoint is hit
$results.Count | Should -Be 7
}
It 'Write-Warning should trigger a breakpoint from -WarningAction Break' {
$results[0] | ShouldHaveExtent -Line 5 -FromColumn 21 -ToColumn 71
}
It 'Write-Information should trigger a breakpoint from -InformationAction Break' {
$results[1] | ShouldHaveExtent -Line 8 -FromColumn 21 -ToColumn 84
}
It 'Write-Warning should trigger a breakpoint from $WarningPreference = [System.Management.Automation.ActionPreference]::Break' {
$results[2] | ShouldHaveExtent -Line 5 -FromColumn 21 -ToColumn 71
}
It 'Write-Verbose should trigger a breakpoint from $VerbosePreference = [System.Management.Automation.ActionPreference]::Break' {
$results[3] | ShouldHaveExtent -Line 6 -FromColumn 21 -ToColumn 71
}
It 'Write-Debug should trigger a breakpoint from $DebugPreference = [System.Management.Automation.ActionPreference]::Break' {
$results[4] | ShouldHaveExtent -Line 7 -FromColumn 21 -ToColumn 67
}
It 'Write-Information should trigger a breakpoint from $InformationPreference = [System.Management.Automation.ActionPreference]::Break' {
$results[5] | ShouldHaveExtent -Line 8 -FromColumn 21 -ToColumn 84
}
It 'Write-Progress should trigger a breakpoint from $ProgressPreference = [System.Management.Automation.ActionPreference]::Break' {
$results[6] | ShouldHaveExtent -Line 9 -FromColumn 21 -ToColumn 67
}
}
Context 'ActionPreference.Break in jobs' {
BeforeAll {
$job = Start-Job {
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Break
Get-Process -TheAnswer 42
}
}
AfterAll {
Remove-Job -Job $job -Force
}
It 'ActionPreference.Break should break in a running job' {
Wait-UntilTrue -sb { $job.State -eq 'AtBreakpoint' } -TimeoutInMilliseconds (10 * 1000) -IntervalInMilliseconds 100 | Should -BeTrue
}
}
}

View file

@ -1,6 +1,56 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Describe 'Basic debugger tests' -tag 'CI' {
BeforeAll {
Register-DebuggerHandler
}
AfterAll {
Unregister-DebuggerHandler
}
Context 'The value of $? should be preserved when exiting the debugger' {
BeforeAll {
$testScript = {
function Test-DollarQuestionMark {
[CmdletBinding()]
param()
Get-Process -id ([int]::MaxValue)
if (-not $?) {
'The value of $? was preserved during debugging.'
} else {
'The value of $? was changed to $true during debugging.'
}
}
$global:DollarQuestionMarkResults = Test-DollarQuestionMark -ErrorAction Break
}
$global:results = @(Test-Debugger -ScriptBlock $testScript -CommandQueue '$?')
}
AfterAll {
Remove-Variable -Name DollarQuestionMarkResults -Scope Global -ErrorAction Ignore
}
It 'Should show 2 debugger commands were invoked' {
# One extra for the implicit 'c' command that keeps the debugger automation moving
$results.Count | Should -Be 2
}
It 'Should have $false output from the first $? command' {
$results[0].Output | Should -BeOfType bool
$results[0].Output | Should -Not -BeTrue
}
It 'Should have string output showing that $? was preserved as $false by the debugger' {
$global:DollarQuestionMarkResults | Should -BeOfType string
$global:DollarQuestionMarkResults | Should -BeExactly 'The value of $? was preserved during debugging.'
}
}
}
Describe "Breakpoints when set should be hit" -tag "CI" {
Context "Basic tests" {
BeforeAll {

View file

@ -0,0 +1,37 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Describe 'Tests for Wait-Debugger' -Tags "CI" {
BeforeAll {
Register-DebuggerHandler
}
AfterAll {
Unregister-DebuggerHandler
}
Context 'Wait-Debugger should break on the statement containing the Wait-Debugger command' {
BeforeAll {
$testScript = {
function Test-Break {
[CmdletBinding()]
param()
Wait-Debugger
'The debugger should break on the previous line, not on this line.'
}
Test-Break
}
$results = @(Test-Debugger -ScriptBlock $testScript)
}
It 'Should show 1 debugger command was invoked' {
# There is always an implicit 'c' command that keeps the debugger automation moving
$results.Count | Should -Be 1
}
It 'The breakpoint should be the statement containing Wait-Debugger' {
$results[0] | ShouldHaveExtent -Line 5 -FromColumn 21 -ToColumn 34
}
}
}

View file

@ -101,14 +101,27 @@ try {
}
It 'cannot invoke a single script asynchronously in a runspace that is busy' {
$ps = [powershell]::Create($Host.Runspace)
$rs = [runspacefactory]::CreateRunspace()
try {
# This test is designed to fail. You cannot invoke PowerShell asynchronously
# in a runspace that is busy, because pipelines cannot be run concurrently.
$err = { InvokeAsyncHelper -PowerShell $ps -Wait } | Should -Throw -ErrorId 'AggregateException' -PassThru
GetInnerErrorId -Exception $err.Exception | Should -Be 'InvalidOperation'
$rs.Open();
$ps1 = [powershell]::Create($rs)
$ps2 = [powershell]::Create($rs)
try {
# Make the runspace busy by running an async command.
$ps1.AddScript('@(1..100).foreach{Start-Sleep -Milliseconds 250}').InvokeAsync()
Wait-UntilTrue { $rs.RunspaceAvailability -eq [System.Management.Automation.Runspaces.RunspaceAvailability]::Busy } | Should -BeTrue
# This test is designed to fail. You cannot invoke PowerShell asynchronously
# in a runspace that is busy, because pipelines cannot be run concurrently.
$err = { InvokeAsyncHelper -PowerShell $ps2 -Wait } | Should -Throw -ErrorId 'AggregateException' -PassThru
GetInnerErrorId -Exception $err.Exception | Should -Be 'InvalidOperation'
} finally {
$ps1.Stop()
$ps1.Dispose()
$ps2.Dispose()
}
} finally {
$ps.Dispose()
$rs.Dispose()
}
}

View file

@ -30,14 +30,19 @@ $debuggerStopHandler = {
$stringDbgCommand = $script:dbgCmdQueue.Dequeue()
}
$dbgCmd = [System.Management.Automation.PSCommand]::new()
$dbgCmd.AddCommand($stringDbgCommand)
$dbgCmd.AddScript($stringDbgCommand) > $null
$output = [System.Management.Automation.PSDataCollection[PSObject]]::new()
$result = $Host.Runspace.Debugger.ProcessCommand($dbgCmd, $output)
if ($stringDbgCommand -eq '$?' -and $output.Count -eq 1) {
$output[0] = $PSDebugContext.Trigger -isnot [System.Management.Automation.ErrorRecord]
}
$script:dbgResults += [pscustomobject]@{
PSTypeName = 'DebuggerCommandResult'
Command = $stringDbgCommand
Context = $PSDebugContext
Output = $output
PSTypeName = 'DebuggerCommandResult'
Command = $stringDbgCommand
Context = $PSDebugContext
Output = $output
EvaluatedByDebugger = $result.EvaluatedByDebugger
ResumeAction = $result.ResumeAction
}
} while ($result -eq $null -or $result.ResumeAction -eq $null)
$e.ResumeAction = $result.ResumeAction