Skip to content
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 #2 #810

Merged
merged 22 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
262d38f
Reinstated public access to adding accessor properties
Jither Nov 30, 2020
31f8221
Distinguish breakpoints by source
Jither Nov 30, 2020
3aa501b
Support let/const
Jither Nov 30, 2020
2d63e20
Improved naming of call stack functions
Jither Nov 30, 2020
be107cd
Early tests of scope of variable collections
Jither Nov 30, 2020
deb033a
Break point tests
Jither Nov 30, 2020
4cc6529
Early call stack function naming tests
Jither Nov 30, 2020
e81fdda
Debugger statement handling (new option)
Jither Nov 30, 2020
7d2510f
Missing file in previous commit
Jither Nov 30, 2020
fb189fc
More call stack function name tests
Jither Nov 30, 2020
40fe7f1
StepMode tests, including failing test for stepping over call expression
Jither Nov 30, 2020
78a33aa
Make StepMode.Over work on all Statements with CallExpressions
Jither Nov 30, 2020
5829d10
Merge branch 'dev' into debugger-improvements
Jither Nov 30, 2020
e4229fa
Removed ObjectInstanceExtensions - redundant after #804
Jither Nov 30, 2020
48744d2
Quick fix for Step Over over-eagerness
Jither Dec 1, 2020
1295d2e
Clean-up of DebugHandler logic
Jither Dec 1, 2020
1b6c2e5
Do Jint handling of debugger statement in DebugHandler
Jither Dec 1, 2020
672137a
Fix erratic StepOver after breakpoint
Jither Dec 1, 2020
7789926
Yet another simplification of step mode logic
Jither Dec 1, 2020
87ba49e
Moved debugger handling back into JintDebuggerStatement (I know...)
Jither Dec 1, 2020
268a37d
Removed pointless default for DebuggerStatementHandling
Jither Dec 1, 2020
b1c424c
Moved DebuggerStatementHandling to Debugger namespace
Jither Dec 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
170 changes: 170 additions & 0 deletions Jint.Tests/Runtime/Debugger/BreakPointTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using Esprima;
using Jint.Runtime.Debugger;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Xunit;

namespace Jint.Tests.Runtime.Debugger
{
public class BreakPointTests
{
[Fact]
public void BreakPointBreaksAtPosition()
{
string script = @"let x = 1, y = 2;
if (x === 1)
{
x++; y *= 2;
}";

var engine = new Engine(options => options.DebugMode());

bool didBreak = false;
engine.Break += (sender, info) =>
{
Assert.Equal(4, info.CurrentStatement.Location.Start.Line);
Assert.Equal(5, info.CurrentStatement.Location.Start.Column);
didBreak = true;
return StepMode.None;
};

engine.BreakPoints.Add(new BreakPoint(4, 5));
engine.Execute(script);
Assert.True(didBreak);
}

[Fact]
public void BreakPointBreaksInCorrectSource()
{
string script1 = @"let x = 1, y = 2;
if (x === 1)
{
x++; y *= 2;
}";

string script2 = @"function test(x)
{
return x + 2;
}";

string script3 = @"const z = 3;
test(z);";

var engine = new Engine(options => { options.DebugMode(); });

engine.BreakPoints.Add(new BreakPoint("script2", 3, 0));

bool didBreak = false;
engine.Break += (sender, info) =>
{
Assert.Equal("script2", info.CurrentStatement.Location.Source);
Assert.Equal(3, info.CurrentStatement.Location.Start.Line);
Assert.Equal(0, info.CurrentStatement.Location.Start.Column);
didBreak = true;
return StepMode.None;
};

// We need to specify the source to the parser.
// And we need locations too (Jint specifies that in its default options)
engine.Execute(script1, new ParserOptions("script1") { Loc = true });
Assert.False(didBreak);

engine.Execute(script2, new ParserOptions("script2") { Loc = true });
Assert.False(didBreak);

// Note that it's actually script3 that executes the function in script2
// and triggers the breakpoint
engine.Execute(script3, new ParserOptions("script3") { Loc = true });
Assert.True(didBreak);
}

[Fact]
public void DebuggerStatementTriggersBreak()
{
string script = @"'dummy';
debugger;
'dummy';";

var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
return StepMode.None;
};

engine.Execute(script);

Assert.True(didBreak);
}

[Fact(Skip = "Non-source breakpoint is triggered before Statement, while debugger statement is now triggered by ExecuteInternal")]
public void DebuggerStatementAndBreakpointTriggerSingleBreak()
{
string script = @"'dummy';
debugger;
'dummy';";

var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

engine.BreakPoints.Add(new BreakPoint(2, 0));

int breakTriggered = 0;
engine.Break += (sender, info) =>
{
breakTriggered++;
return StepMode.None;
};

engine.Execute(script);

Assert.Equal(1, breakTriggered);
}

[Fact]
public void BreakpointOverridesStepOut()
{
string script = @"function test()
{
'dummy';
'source';
'dummy';
'target';
}
test();";

var engine = new Engine(options => options.DebugMode());

engine.BreakPoints.Add(new BreakPoint(4, 0));
engine.BreakPoints.Add(new BreakPoint(6, 0));

int step = 0;
engine.Break += (sender, info) =>
{
step++;
switch (step)
{
case 1:
return StepMode.Out;
case 2:
Assert.True(info.ReachedLiteral("target"));
break;
}
return StepMode.None;
};

engine.Execute(script);

Assert.Equal(2, step);
}
}
}
228 changes: 228 additions & 0 deletions Jint.Tests/Runtime/Debugger/CallStackTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
using Jint.Runtime.Debugger;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace Jint.Tests.Runtime.Debugger
{
public class CallStackTests
{
[Fact]
public void NamesRegularFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("regularFunction", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"function regularFunction() { debugger; }
regularFunction()");

Assert.True(didBreak);
}

[Fact]
public void NamesFunctionExpression()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("functionExpression", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const functionExpression = function() { debugger; }
functionExpression()");

Assert.True(didBreak);
}

[Fact]
public void NamesNamedFunctionExpression()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("namedFunction", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const functionExpression = function namedFunction() { debugger; }
functionExpression()");

Assert.True(didBreak);
}

[Fact]
public void NamesArrowFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("arrowFunction", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const arrowFunction = () => { debugger; }
arrowFunction()");

Assert.True(didBreak);
}

[Fact]
public void NamesNewFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
// Ideally, this should be "(anonymous)", but FunctionConstructor sets the "anonymous" name.
Assert.Equal("anonymous", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const newFunction = new Function('debugger;');
newFunction()");

Assert.True(didBreak);
}

[Fact]
public void NamesMemberFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("memberFunction", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const obj = { memberFunction() { debugger; } };
obj.memberFunction()");

Assert.True(didBreak);
}

[Fact]
public void NamesAnonymousFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("(anonymous)", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"(function()
{
debugger;
}());");

Assert.True(didBreak);
}

[Fact(Skip = "Debugger has no accessor awareness yet")]
public void NamesGetAccessor()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("get accessor", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"
const obj = {
get accessor()
{
debugger;
return 'test';
}
};
const x = obj.accessor;");

Assert.True(didBreak);
}

[Fact(Skip = "Debugger has no accessor awareness yet")]
public void NamesSetAccessor()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Jint));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("set accessor", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"
const obj = {
set accessor(value)
{
debugger;
this.value = value;
}
};
obj.accessor = 42;");

Assert.True(didBreak);
}
}
}