DebugHandler.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Esprima.Ast;
  4. using Jint.Native;
  5. using Jint.Native.Function;
  6. using Jint.Runtime.Descriptors;
  7. using Jint.Runtime.Environments;
  8. using Jint.Runtime.Interop;
  9. namespace Jint.Runtime.Debugger
  10. {
  11. internal class DebugHandler
  12. {
  13. private readonly Stack<string> _debugCallStack;
  14. private int _steppingDepth;
  15. private readonly Engine _engine;
  16. public DebugHandler(Engine engine)
  17. {
  18. _engine = engine;
  19. _debugCallStack = new Stack<string>();
  20. _steppingDepth = int.MaxValue;
  21. }
  22. internal void AddToDebugCallStack(JsValue function, CallExpression callExpression)
  23. {
  24. string name = GetCalleeName(function, callExpression.Callee);
  25. _debugCallStack.Push(name);
  26. }
  27. internal void PopDebugCallStack()
  28. {
  29. if (_debugCallStack.Count > 0)
  30. {
  31. _debugCallStack.Pop();
  32. }
  33. }
  34. private string GetCalleeName(JsValue function, Expression calleeExpression)
  35. {
  36. switch (function)
  37. {
  38. case DelegateWrapper _:
  39. return "(native code)";
  40. case FunctionInstance instance:
  41. PropertyDescriptor nameDescriptor = instance.GetOwnProperty(CommonProperties.Name);
  42. JsValue nameValue = nameDescriptor != null ? instance.UnwrapJsValue(nameDescriptor) : JsString.Empty;
  43. return !nameValue.IsUndefined() ? TypeConverter.ToString(nameValue) : "(anonymous)";
  44. default:
  45. return "(unknown)";
  46. }
  47. }
  48. internal void OnStep(Statement statement)
  49. {
  50. BreakPoint breakpoint = _engine.BreakPoints.FirstOrDefault(breakPoint => BpTest(statement, breakPoint));
  51. if (breakpoint != null)
  52. {
  53. Break(statement);
  54. }
  55. else if (_debugCallStack.Count <= _steppingDepth)
  56. {
  57. Step(statement);
  58. }
  59. }
  60. private void Step(Statement statement)
  61. {
  62. DebugInformation info = CreateDebugInformation(statement);
  63. StepMode? result = _engine.InvokeStepEvent(info);
  64. HandleNewStepMode(result);
  65. }
  66. internal void Break(Statement statement)
  67. {
  68. DebugInformation info = CreateDebugInformation(statement);
  69. StepMode? result = _engine.InvokeBreakEvent(info);
  70. HandleNewStepMode(result);
  71. }
  72. private void HandleNewStepMode(StepMode? newStepMode)
  73. {
  74. if (newStepMode != null)
  75. {
  76. switch (newStepMode)
  77. {
  78. case StepMode.Over:
  79. // Resume stepping when we're back at this level of the call stack
  80. _steppingDepth = _debugCallStack.Count;
  81. break;
  82. case StepMode.Out:
  83. // Resume stepping when we've popped the call stack
  84. _steppingDepth = _debugCallStack.Count - 1;
  85. break;
  86. case StepMode.None:
  87. // Never step
  88. _steppingDepth = int.MinValue;
  89. break;
  90. default:
  91. // Always step
  92. _steppingDepth = int.MaxValue;
  93. break;
  94. }
  95. }
  96. }
  97. private bool BpTest(Statement statement, BreakPoint breakpoint)
  98. {
  99. if (breakpoint.Source != null)
  100. {
  101. if (breakpoint.Source != statement.Location.Source)
  102. {
  103. return false;
  104. }
  105. }
  106. bool afterStart, beforeEnd;
  107. afterStart = (breakpoint.Line == statement.Location.Start.Line &&
  108. breakpoint.Char >= statement.Location.Start.Column);
  109. if (!afterStart)
  110. {
  111. return false;
  112. }
  113. beforeEnd = breakpoint.Line < statement.Location.End.Line
  114. || (breakpoint.Line == statement.Location.End.Line &&
  115. breakpoint.Char <= statement.Location.End.Column);
  116. if (!beforeEnd)
  117. {
  118. return false;
  119. }
  120. if (!string.IsNullOrEmpty(breakpoint.Condition))
  121. {
  122. var completionValue = _engine.Execute(breakpoint.Condition).GetCompletionValue();
  123. return ((JsBoolean) completionValue)._value;
  124. }
  125. return true;
  126. }
  127. private DebugInformation CreateDebugInformation(Statement statement)
  128. {
  129. var info = new DebugInformation
  130. {
  131. CurrentStatement = statement,
  132. CallStack = _debugCallStack,
  133. CurrentMemoryUsage = _engine.CurrentMemoryUsage
  134. };
  135. info.Locals = GetLocalVariables(_engine.ExecutionContext);
  136. info.Globals = GetGlobalVariables(_engine.ExecutionContext);
  137. return info;
  138. }
  139. private static Dictionary<string, JsValue> GetLocalVariables(ExecutionContext context)
  140. {
  141. Dictionary<string, JsValue> locals = new Dictionary<string, JsValue>();
  142. // Local variables are the union of function scope (VariableEnvironment)
  143. // and any current block scope (LexicalEnvironment)
  144. if (!ReferenceEquals(context.VariableEnvironment?._record, null))
  145. {
  146. AddRecordsFromEnvironment(context.VariableEnvironment, locals);
  147. }
  148. if (!ReferenceEquals(context.LexicalEnvironment?._record, null))
  149. {
  150. AddRecordsFromEnvironment(context.LexicalEnvironment, locals);
  151. }
  152. return locals;
  153. }
  154. private static Dictionary<string, JsValue> GetGlobalVariables(ExecutionContext context)
  155. {
  156. Dictionary<string, JsValue> globals = new Dictionary<string, JsValue>();
  157. // Unless we're in the global scope (_outer is null), don't include function local variables.
  158. // The function local variables are in the variable environment (function scope) and any current
  159. // lexical environment (block scope), which will be a "child" of that VariableEnvironment.
  160. // Hence, we should only use the VariableEnvironment's outer environment for global scope. This
  161. // also means that block scoped variables will never be included - they'll be listed as local variables.
  162. LexicalEnvironment tempLex = context.VariableEnvironment._outer ?? context.VariableEnvironment;
  163. while (!ReferenceEquals(tempLex?._record, null))
  164. {
  165. AddRecordsFromEnvironment(tempLex, globals);
  166. tempLex = tempLex._outer;
  167. }
  168. return globals;
  169. }
  170. private static void AddRecordsFromEnvironment(LexicalEnvironment lex, Dictionary<string, JsValue> locals)
  171. {
  172. var bindings = lex._record.GetAllBindingNames();
  173. foreach (var binding in bindings)
  174. {
  175. if (!locals.ContainsKey(binding))
  176. {
  177. var jsValue = lex._record.GetBindingValue(binding, false);
  178. switch (jsValue)
  179. {
  180. case ICallable _:
  181. // TODO: Callables aren't added - but maybe they should be.
  182. break;
  183. case null:
  184. // Uninitialized consts in scope are shown as "undefined" in e.g. Chromium debugger.
  185. // Uninitialized lets aren't displayed.
  186. // TODO: Check if null result from GetBindingValue is only true for uninitialized const/let.
  187. break;
  188. default:
  189. locals.Add(binding, jsValue);
  190. break;
  191. }
  192. }
  193. }
  194. }
  195. }
  196. }