MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.IO;6using System.Linq;7using System.Management.Automation.Internal;8using System.Management.Automation.Language;9using System.Threading;10 11namespace System.Management.Automation12{13 /// <summary>14 /// Holds the information for a given breakpoint.15 /// </summary>16 public abstract class Breakpoint17 {18 #region properties19 20 /// <summary>21 /// The action to take when the breakpoint is hit.22 /// </summary>23 public ScriptBlock Action { get; }24 25 /// <summary>26 /// Gets whether this breakpoint is enabled.27 /// </summary>28 public bool Enabled { get; private set; }29 30 internal void SetEnabled(bool value)31 {32 Enabled = value;33 }34 35 /// <summary>36 /// Records how many times this breakpoint has been triggered.37 /// </summary>38 public int HitCount { get; private set; }39 40 /// <summary>41 /// This breakpoint's Id.42 /// </summary>43 public int Id { get; }44 45 /// <summary>46 /// True if breakpoint is set on a script, false if the breakpoint is not scoped.47 /// </summary>48 internal bool IsScriptBreakpoint49 {50 get { return Script != null; }51 }52 53 /// <summary>54 /// The script this breakpoint is on, or null if the breakpoint is not scoped.55 /// </summary>56 public string Script { get; }57 58 #endregion properties59 60 #region constructors61 62 /// <summary>63 /// Creates a new instance of a <see cref="Breakpoint"/>64 /// </summary>65 protected Breakpoint(string script)66 : this(script, null)67 { }68 69 /// <summary>70 /// Creates a new instance of a <see cref="Breakpoint"/>71 /// </summary>72 protected Breakpoint(string script, ScriptBlock action)73 {74 Enabled = true;75 Script = string.IsNullOrEmpty(script) ? null : script;76 Id = Interlocked.Increment(ref s_lastID);77 Action = action;78 HitCount = 0;79 }80 81 /// <summary>82 /// Creates a new instance of a <see cref="Breakpoint"/>83 /// </summary>84 protected Breakpoint(string script, int id)85 : this(script, null, id)86 { }87 88 /// <summary>89 /// Creates a new instance of a <see cref="Breakpoint"/>90 /// </summary>91 protected Breakpoint(string script, ScriptBlock action, int id)92 {93 Enabled = true;94 Script = string.IsNullOrEmpty(script) ? null : script;95 Id = id;96 Action = action;97 HitCount = 0;98 }99 100 #endregion constructors101 102 #region methods103 104 internal BreakpointAction Trigger()105 {106 ++HitCount;107 if (Action == null)108 {109 return BreakpointAction.Break;110 }111 112 try113 {114 // Pass this to the action so the breakpoint. This could be used to115 // implement a "trigger once" breakpoint that disables itself after first hit.116 // One could also share an action across many breakpoints - and hence needs117 // to know something about the breakpoint that is hit, e.g. in a poor mans code coverage tool.118 Action.DoInvoke(dollarUnder: this, input: null, args: Array.Empty<object>());119 }120 catch (BreakException)121 {122 return BreakpointAction.Break;123 }124 catch (Exception)125 {126 }127 128 return BreakpointAction.Continue;129 }130 131 internal virtual bool RemoveSelf(ScriptDebugger debugger) => false;132 133 #endregion methods134 135 #region enums136 137 internal enum BreakpointAction138 {139 Continue = 0x0,140 Break = 0x1141 }142 143 #endregion enums144 145 #region private members146 147 private static int s_lastID;148 149 #endregion private members150 }151 152 /// <summary>153 /// A breakpoint on a command.154 /// </summary>155 public class CommandBreakpoint : Breakpoint156 {157 /// <summary>158 /// Creates a new instance of a <see cref="CommandBreakpoint"/>159 /// </summary>160 public CommandBreakpoint(string script, WildcardPattern command, string commandString)161 : this(script, command, commandString, null)162 { }163 164 /// <summary>165 /// Creates a new instance of a <see cref="CommandBreakpoint"/>166 /// </summary>167 public CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action)168 : base(script, action)169 {170 CommandPattern = command;171 Command = commandString;172 }173 174 /// <summary>175 /// Creates a new instance of a <see cref="CommandBreakpoint"/>176 /// </summary>177 public CommandBreakpoint(string script, WildcardPattern command, string commandString, int id)178 : this(script, command, commandString, null, id)179 { }180 181 /// <summary>182 /// Creates a new instance of a <see cref="CommandBreakpoint"/>183 /// </summary>184 public CommandBreakpoint(string script, WildcardPattern command, string commandString, ScriptBlock action, int id)185 : base(script, action, id)186 {187 CommandPattern = command;188 Command = commandString;189 }190 191 /// <summary>192 /// Which command this breakpoint is on.193 /// </summary>194 public string Command { get; }195 196 internal WildcardPattern CommandPattern { get; }197 198 /// <summary>199 /// Gets a string representation of this breakpoint.200 /// </summary>201 /// <returns>A string representation of this breakpoint.</returns>202 public override string ToString()203 {204 return IsScriptBreakpoint205 ? StringUtil.Format(DebuggerStrings.CommandScriptBreakpointString, Script, Command)206 : StringUtil.Format(DebuggerStrings.CommandBreakpointString, Command);207 }208 209 internal override bool RemoveSelf(ScriptDebugger debugger) =>210 debugger.RemoveCommandBreakpoint(this);211 212 private bool CommandInfoMatches(CommandInfo commandInfo)213 {214 if (commandInfo == null)215 return false;216 217 if (CommandPattern.IsMatch(commandInfo.Name))218 return true;219 220 // If the breakpoint looks like it might have specified a module name and the command221 // we're checking is in a module, try matching the module\command against the pattern222 // in the breakpoint.223 if (!string.IsNullOrEmpty(commandInfo.ModuleName) && Command.Contains('\\'))224 {225 if (CommandPattern.IsMatch(commandInfo.ModuleName + "\\" + commandInfo.Name))226 return true;227 }228 229 var externalScript = commandInfo as ExternalScriptInfo;230 if (externalScript != null)231 {232 if (externalScript.Path.Equals(Command, StringComparison.OrdinalIgnoreCase))233 return true;234 235 if (CommandPattern.IsMatch(Path.GetFileNameWithoutExtension(externalScript.Path)))236 return true;237 }238 239 return false;240 }241 242 internal bool Trigger(InvocationInfo invocationInfo)243 {244 // invocationInfo.MyCommand can be null when invoked via ScriptBlock.Invoke()245 if (CommandPattern.IsMatch(invocationInfo.InvocationName) || CommandInfoMatches(invocationInfo.MyCommand))246 {247 return (Script == null || Script.Equals(invocationInfo.ScriptName, StringComparison.OrdinalIgnoreCase));248 }249 250 return false;251 }252 }253 254 /// <summary>255 /// The access type for variable breakpoints to break on.256 /// </summary>257 public enum VariableAccessMode258 {259 /// <summary>260 /// Break on read access only.261 /// </summary>262 Read,263 /// <summary>264 /// Break on write access only (default).265 /// </summary>266 Write,267 /// <summary>268 /// Breakon read or write access.269 /// </summary>270 ReadWrite271 }272 273 /// <summary>274 /// A breakpoint on a variable.275 /// </summary>276 public class VariableBreakpoint : Breakpoint277 {278 /// <summary>279 /// Creates a new instance of a <see cref="VariableBreakpoint"/>.280 /// </summary>281 public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode)282 : this(script, variable, accessMode, null)283 { }284 285 /// <summary>286 /// Creates a new instance of a <see cref="VariableBreakpoint"/>.287 /// </summary>288 public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action)289 : base(script, action)290 {291 Variable = variable;292 AccessMode = accessMode;293 }294 295 /// <summary>296 /// Creates a new instance of a <see cref="VariableBreakpoint"/>.297 /// </summary>298 public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, int id)299 : this(script, variable, accessMode, null, id)300 { }301 302 /// <summary>303 /// Creates a new instance of a <see cref="VariableBreakpoint"/>.304 /// </summary>305 public VariableBreakpoint(string script, string variable, VariableAccessMode accessMode, ScriptBlock action, int id)306 : base(script, action, id)307 {308 Variable = variable;309 AccessMode = accessMode;310 }311 312 /// <summary>313 /// The access mode to trigger this variable breakpoint on.314 /// </summary>315 public VariableAccessMode AccessMode { get; }316 317 /// <summary>318 /// Which variable this breakpoint is on.319 /// </summary>320 public string Variable { get; }321 322 /// <summary>323 /// Gets the string representation of this breakpoint.324 /// </summary>325 /// <returns>The string representation of this breakpoint.</returns>326 public override string ToString()327 {328 return IsScriptBreakpoint329 ? StringUtil.Format(DebuggerStrings.VariableScriptBreakpointString, Script, Variable, AccessMode)330 : StringUtil.Format(DebuggerStrings.VariableBreakpointString, Variable, AccessMode);331 }332 333 internal bool Trigger(string currentScriptFile, bool read)334 {335 if (!Enabled)336 return false;337 338 if (AccessMode != VariableAccessMode.ReadWrite && AccessMode != (read ? VariableAccessMode.Read : VariableAccessMode.Write))339 return false;340 341 if (Script == null || Script.Equals(currentScriptFile, StringComparison.OrdinalIgnoreCase))342 {343 return Trigger() == BreakpointAction.Break;344 }345 346 return false;347 }348 349 internal override bool RemoveSelf(ScriptDebugger debugger) =>350 debugger.RemoveVariableBreakpoint(this);351 }352 353 /// <summary>354 /// A breakpoint on a line or statement.355 /// </summary>356 public class LineBreakpoint : Breakpoint357 {358 /// <summary>359 /// Creates a new instance of a <see cref="LineBreakpoint"/>360 /// </summary>361 public LineBreakpoint(string script, int line)362 : this(script, line, null)363 { }364 365 /// <summary>366 /// Creates a new instance of a <see cref="LineBreakpoint"/>367 /// </summary>368 public LineBreakpoint(string script, int line, ScriptBlock action)369 : base(script, action)370 {371 Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty.");372 Line = line;373 Column = 0;374 SequencePointIndex = -1;375 }376 377 /// <summary>378 /// Creates a new instance of a <see cref="LineBreakpoint"/>379 /// </summary>380 public LineBreakpoint(string script, int line, int column)381 : this(script, line, column, null)382 { }383 384 /// <summary>385 /// Creates a new instance of a <see cref="LineBreakpoint"/>386 /// </summary>387 public LineBreakpoint(string script, int line, int column, ScriptBlock action)388 : base(script, action)389 {390 Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty.");391 Line = line;392 Column = column;393 SequencePointIndex = -1;394 }395 396 /// <summary>397 /// Creates a new instance of a <see cref="LineBreakpoint"/>398 /// </summary>399 public LineBreakpoint(string script, int line, int column, int id)400 : this(script, line, column, null, id)401 { }402 403 /// <summary>404 /// Creates a new instance of a <see cref="LineBreakpoint"/>405 /// </summary>406 public LineBreakpoint(string script, int line, int column, ScriptBlock action, int id)407 : base(script, action, id)408 {409 Diagnostics.Assert(!string.IsNullOrEmpty(script), "Caller to verify script parameter is not null or empty.");410 Line = line;411 Column = column;412 SequencePointIndex = -1;413 }414 415 /// <summary>416 /// Which column this breakpoint is on.417 /// </summary>418 public int Column { get; }419 420 /// <summary>421 /// Which line this breakpoint is on.422 /// </summary>423 public int Line { get; }424 425 /// <summary>426 /// Gets a string representation of this breakpoint.427 /// </summary>428 /// <returns>A string representation of this breakpoint.</returns>429 public override string ToString()430 {431 return Column == 0432 ? StringUtil.Format(DebuggerStrings.LineBreakpointString, Script, Line)433 : StringUtil.Format(DebuggerStrings.StatementBreakpointString, Script, Line, Column);434 }435 436 internal int SequencePointIndex { get; set; }437 438 internal IScriptExtent[] SequencePoints { get; set; }439 440 internal BitArray BreakpointBitArray { get; set; }441 442 private sealed class CheckBreakpointInScript : AstVisitor443 {444 public static bool IsInNestedScriptBlock(Ast ast, LineBreakpoint breakpoint)445 {446 var visitor = new CheckBreakpointInScript { _breakpoint = breakpoint };447 ast.InternalVisit(visitor);448 return visitor._result;449 }450 451 private LineBreakpoint _breakpoint;452 private bool _result;453 454 public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)455 {456 if (functionDefinitionAst.Extent.ContainsLineAndColumn(_breakpoint.Line, _breakpoint.Column))457 {458 _result = true;459 return AstVisitAction.StopVisit;460 }461 462 // We don't need to visit the body, we're just checking extents of the topmost functions.463 // We'll visit the bodies eventually, but only when the nested function/script is executed.464 return AstVisitAction.SkipChildren;465 }466 467 public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)468 {469 if (scriptBlockExpressionAst.Extent.ContainsLineAndColumn(_breakpoint.Line, _breakpoint.Column))470 {471 _result = true;472 return AstVisitAction.StopVisit;473 }474 475 // We don't need to visit the body, we're just checking extents of the topmost functions.476 // We'll visit the bodies eventually, but only when the nested function/script is executed.477 return AstVisitAction.SkipChildren;478 }479 }480 481 internal bool TrySetBreakpoint(string scriptFile, FunctionContext functionContext)482 {483 Diagnostics.Assert(SequencePointIndex == -1, "shouldn't be trying to set on a pending breakpoint");484 485 // A quick check to see if the breakpoint is within the scriptblock.486 bool couldBeInNestedScriptBlock;487 var scriptBlock = functionContext._scriptBlock;488 if (scriptBlock != null)489 {490 var ast = scriptBlock.Ast;491 if (!ast.Extent.ContainsLineAndColumn(Line, Column))492 return false;493 494 var sequencePoints = functionContext._sequencePoints;495 if (sequencePoints.Length == 1 && sequencePoints[0] == scriptBlock.Ast.Extent)496 {497 // If there was no real executable code in the function (e.g. only function definitions),498 // we added the entire scriptblock as a sequence point, but it shouldn't be allowed as a breakpoint.499 return false;500 }501 502 couldBeInNestedScriptBlock = CheckBreakpointInScript.IsInNestedScriptBlock(((IParameterMetadataProvider)ast).Body, this);503 }504 else505 {506 couldBeInNestedScriptBlock = false;507 }508 509 int sequencePointIndex;510 var sequencePoint = FindSequencePoint(functionContext, Line, Column, out sequencePointIndex);511 if (sequencePoint != null)512 {513 // If the bp could be in a nested script block, we want to be careful and get the bp in the correct script block.514 // If it's a simple line bp (no column specified), then the start line must match the bp line exactly, otherwise515 // we assume the bp is in the nested script block.516 if (!couldBeInNestedScriptBlock || (sequencePoint.StartLineNumber == Line && Column == 0))517 {518 SetBreakpoint(functionContext, sequencePointIndex);519 return true;520 }521 }522 523 // Before using heuristics, make sure the breakpoint isn't in a nested function/script block.524 if (couldBeInNestedScriptBlock)525 {526 return false;527 }528 529 // Not found. First, we check if the line/column is before any real code. If so, we'll530 // move the breakpoint to the first interesting sequence point (could be a dynamicparam,531 // begin, process, end, or clean block.)532 if (scriptBlock != null)533 {534 var ast = scriptBlock.Ast;535 var bodyAst = ((IParameterMetadataProvider)ast).Body;536 if ((bodyAst.DynamicParamBlock == null || bodyAst.DynamicParamBlock.Extent.IsAfter(Line, Column))537 && (bodyAst.BeginBlock == null || bodyAst.BeginBlock.Extent.IsAfter(Line, Column))538 && (bodyAst.ProcessBlock == null || bodyAst.ProcessBlock.Extent.IsAfter(Line, Column))539 && (bodyAst.EndBlock == null || bodyAst.EndBlock.Extent.IsAfter(Line, Column))540 && (bodyAst.CleanBlock == null || bodyAst.CleanBlock.Extent.IsAfter(Line, Column)))541 {542 SetBreakpoint(functionContext, 0);543 return true;544 }545 }546 547 // Still not found. Try fudging a bit, but only if it's a simple line breakpoint.548 if (Column == 0 && FindSequencePoint(functionContext, Line + 1, 0, out sequencePointIndex) != null)549 {550 SetBreakpoint(functionContext, sequencePointIndex);551 return true;552 }553 554 return false;555 }556 557 private static IScriptExtent FindSequencePoint(FunctionContext functionContext, int line, int column, out int sequencePointIndex)558 {559 var sequencePoints = functionContext._sequencePoints;560 561 for (int i = 0; i < sequencePoints.Length; ++i)562 {563 var extent = sequencePoints[i];564 if (extent.ContainsLineAndColumn(line, column))565 {566 sequencePointIndex = i;567 return extent;568 }569 }570 571 sequencePointIndex = -1;572 return null;573 }574 575 private void SetBreakpoint(FunctionContext functionContext, int sequencePointIndex)576 {577 // Remember the bitarray so we when the last breakpoint is removed, we can avoid578 // stopping at the sequence point.579 this.BreakpointBitArray = functionContext._breakPoints;580 this.SequencePoints = functionContext._sequencePoints;581 582 SequencePointIndex = sequencePointIndex;583 this.BreakpointBitArray.Set(SequencePointIndex, true);584 }585 586 internal override bool RemoveSelf(ScriptDebugger debugger)587 {588 if (this.SequencePoints != null)589 {590 // Remove ourselves from the list of bound breakpoints in this script. It's possible the breakpoint was never591 // bound, in which case there is nothing to do.592 var boundBreakPoints = debugger.GetBoundBreakpoints(this.SequencePoints);593 if (boundBreakPoints != null)594 {595 Diagnostics.Assert(boundBreakPoints[this.SequencePointIndex].Contains(this),596 "If we set _scriptBlock, we should have also added the breakpoint to the bound breakpoint list");597 boundBreakPoints[this.SequencePointIndex].Remove(this);598 599 if (boundBreakPoints[this.SequencePointIndex].All(breakpoint => breakpoint.SequencePointIndex != this.SequencePointIndex))600 {601 // No other line breakpoints are at the same sequence point, so disable the breakpoint so602 // we don't go looking for breakpoints the next time we hit the sequence point.603 // This isn't strictly necessary, but script execution will be faster.604 this.BreakpointBitArray.Set(SequencePointIndex, false);605 }606 }607 }608 609 return debugger.RemoveLineBreakpoint(this);610 }611 }612}613 