-
-
Notifications
You must be signed in to change notification settings - Fork 568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Debugger improvements 4b #1113
Debugger improvements 4b #1113
Conversation
BindingObject property on Global and With scopes (mainly for devtools for now, but may be useful for VSCode DebugAdapter)
Only allows a single breakpoint at a given location (source/line/column) - hence, API is now "Set" instead of "Add". Requires the breakpoint to be set at *exactly* the correct line/column (this allows faster matching, column breakpoints, and more). Fixed tests broken by addition of Script scope and new breakpoint semantics. Breakpoints still need work on the API (and probably implementation) side. Additional BreakPoint tests
Test case for failing breakpoint conditions (currently they, and other evaluations during paused state) reset the call stack (and probably other things). Documentation of SourceParsedEventArgs
Renamed DebugHandler OnBreak event to clarify that it only handles debugger statements. Added PauseType (Break/Step/DebuggerStatement) to DebugInformation to allow debuggers to distinguish between breaks and debugger statements.
Removed unused Source property from SourceParsedEventArgs
Removed redundant BreakPoint constructors
Option for initial StepMode - it's now StepMode.None by default to properly handle breakpoints from the start. Previously, it was StepMode.Into Updated tests to reflect new initial StepMode
Error messages for a few TypeErrors
DebugHandler cleanup and more documentation
Currently unused, and a better approach for getting AST for scripts is needed, in order to support Modules etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good and great to see such test coverage, you've really put the effort in! I added some comments, please see if makes sense to address.
I'm not sure about the DebugHandler's own methods for evaluation of expressions - it's needed in order to reuse the active execution context, and avoid an evaluated expression trashing the engine's call stack. However, it shares most of its code with Engine's DoInvoke (inside Execute). It might be a good idea to combine the two?
It's an option, but can also bind the two needlessly. I think you can make the call 🙂
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Jint.Runtime.Debugger |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use file-scoped namespaces for new files and set #nullable enable to reduce later work
/// Equals returns true if all properties are equal - or if Source is null on either BreakLocation. | ||
/// GetHashCode excludes Source. | ||
/// </remarks> | ||
public sealed class OptionalSourceBreakLocationEqualityComparer : IEqualityComparer<BreakLocation> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be internal, maybe in it's own file (same namespace and nullable rules would apply)
Jint/Runtime/Debugger/BreakPoint.cs
Outdated
public sealed class BreakPoint | ||
// BreakPoint is not sealed. It's useful to be able to add additional properties on a derived BreakPoint class (e.g. a breakpoint ID | ||
// or breakpoint type) but still let it be managed by Jint's breakpoint collection. | ||
public class BreakPoint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there something deriving from this in test project, would make sure nobody (like me) would accidentally seal again, as many changes already could also do the namespace and nullable thing
@@ -0,0 +1,18 @@ | |||
using System; | |||
|
|||
namespace Jint.Runtime.Debugger |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use file-scoped namespaces for new files and set #nullable enable to reduce later work
/// <summary> | ||
/// Thrown when an evaluation executed through the DebugHandler results in any type of error - parsing or runtime. | ||
/// </summary> | ||
public sealed class DebugEvaluationException : Exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this derive from JintException?
@@ -50,6 +50,11 @@ protected override Completion ExecuteInternal(EvaluationContext context) | |||
} | |||
} | |||
|
|||
if (context.Engine._isDebugMode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could promote the value to context.DebugMode
and initialize in context constructor to make code cleaner?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked, and context.DebugMode
is already there - it's just only used once. I've changed all the references to _isDebugMode outside Engine
. Only one that's a small issue is:
if (_engine._isDebugMode && result.Type != CompletionType.Throw) |
I've changed that one to _engine._activeExecutionContext.DebugMode
for consistency - now, only Engine
and the context constructor read the _isDebugMode
field. Could declare context
as a local variable, since _activeExecutionContext is also used a few lines earlier - however, I'm not actually sure if the OrdinaryCallEvaluateBody()
call might conceivably change the value of it - DebugMode
won't care, but anything else that might decide to use that local context
variable would.
Looking at the others (all agreed) tonight.
I guess we'll keep them separate - because, yeah, the |
#nullable enable and namespace statements on new files Slight improvement of DebugEvaluationException Got rid of DebugHandler references to _engine._isDebugMode (use context) A few new tests
@Jither I think we can merge when you feel this is ready 👍🏻 |
@lahma Any time 🙂 - no more changes intended - there's sure to be a
Might also eventually consider easing breakpoint management and possibly some other things -
But that stuff probably doesn't make sense to consider until |
Thanks for the rundown, seems quite clear and reasonable future improvements. Once again, great work and thanks for finalizing 4b 😉 |
And we're back...
I figure we might as well try to find a way to merge this, before I might randomly leave it for another year (although no plans to do so yet, my work assignments are increasing). 🙂 Also, before it gets an (even more) unwieldy amount of changes (although few requirements remain, I think). It's proven its worth and usefulness (to me) on the VSCode DebugAdapter so far, at least.
Much of this is verbatim from the previous pull request: #1018 - so I'll include the change list from that pull request, marking new stuff compared to then with a nice light bulb:
Discussion point 🤔
I'm not sure about the
DebugHandler
's own methods for evaluation of expressions - it's needed in order to reuse the active execution context, and avoid an evaluated expression trashing the engine's call stack. However, it shares most of its code withEngine
'sDoInvoke
(insideExecute
). It might be a good idea to combine the two?Summary of changes
bold areas = breaking changes (breaking for debugger implementations, that is)
Step
andBreak
event behavior.Break
is now no longer triggered while stepping - neither for breakpoints nor for thedebugger
statement. In other words, it's only called ifStepMode
isNone
. Because the same event handler delegate type is used for both, they can easily be combined in a single handler if there's no need to distinguish between the two conditions.DebugHandler
starts withStepMode.None
by default (to make breakpoints "just work"). An option has been added -InitialStepMode
- to allow changing that default.Breakpoint
property is set onDebugInformation
whenever a breakpoint is reached (and its condition is true) - both in theStep
andBreak
events. This allows still handling (and potentially extending functionality of) breakpoints, even if the breakpoint didn't actually triggerBreak
(due to the change above). This is used in theJint.DebugAdapter
to add "hit count/hit condition" functionality (where the counter needs increment even when stepping through the breakpoint), and for log points.PauseType
is set onDebugInformation
to indicate what kind of pause causedBreak
orStep
triggering -Breakpoint
,Step
, orDebuggerStatement
. In future, would be expanded with e.g.Exception
(when/if exceptions are centralized enough for them all to callDebugHandler
before throwing).DebugHandler.Evaluate
. Used by conditional breakpoints, and useful for everything from debugger watch expressions to a console REPL. This also fixes issues with execution context for previous conditional breakpoint implementation.DebugInformation
. While most full debuggers would need it, there's no point allocating and building it for many simpler step/break handlers (including in the unit tests)CurrentLocation
property onDebugHandler
- available even when not stepping/breaking (as long asDebugMode
is enabled). This allows e.g.console
object implementations to include the script location of the call writing to the console.Event** (- this addition from the previous pull request has been removed for now - it needs a better approach to also support Modules.Parsed
) on engine for notification of when scripts have been through Esprima. This avoids the need for double parsing, because debugger implementations are likely to need the AST too (for e.g. determining proper locations for breakpoints).BreakLocation
class.Phew! 😂