Skip to content

Commit

Permalink
Debugger API improvements (#1382)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jither committed Dec 20, 2022
1 parent b5ce1b4 commit 3c45b8c
Show file tree
Hide file tree
Showing 20 changed files with 309 additions and 143 deletions.
28 changes: 28 additions & 0 deletions Jint.Tests/Runtime/Debugger/BreakPointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public void DebuggerStatementTriggersBreak()
bool didBreak = false;
engine.DebugHandler.Break += (sender, info) =>
{
Assert.Equal(PauseType.DebuggerStatement, info.PauseType);
didBreak = true;
return StepMode.None;
};
Expand Down Expand Up @@ -232,6 +233,33 @@ public void DebuggerStatementDoesNotTriggerBreakWhenStepping()
Assert.False(didBreak);
}

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

var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script)
.InitialStepMode(StepMode.None));

int breakCount = 0;

engine.DebugHandler.BreakPoints.Set(new BreakPoint(2, 0));

engine.DebugHandler.Break += (sender, info) =>
{
Assert.Equal(PauseType.Break, info.PauseType);
breakCount++;
return StepMode.None;
};

engine.Execute(script);
Assert.Equal(1, breakCount);
}

[Fact]
public void BreakPointDoesNotTriggerBreakWhenStepping()
{
Expand Down
2 changes: 1 addition & 1 deletion Jint.Tests/Runtime/Debugger/DebugHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void AvoidsPauseRecursion()
if (info.ReachedLiteral("target"))
{
var obj = info.CurrentScopeChain.Global.GetBindingValue("obj") as ObjectInstance;
var obj = info.CurrentScopeChain[0].GetBindingValue("obj") as ObjectInstance;
var prop = obj.GetOwnProperty("name");
// This is where reentrance would occur:
var value = prop.Get.Invoke(engine);
Expand Down
7 changes: 4 additions & 3 deletions Jint.Tests/Runtime/Debugger/ScopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ function power(a)
{
Assert.Collection(info.CurrentScopeChain,
scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a"),
scope => AssertScope(scope, DebugScopeType.Closure, "b", "power"), // a, this, arguments shadowed by local
// a, arguments shadowed by local - but still exist in this scope
scope => AssertScope(scope, DebugScopeType.Closure, "a", "arguments", "b", "power"),
scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"),
scope => AssertScope(scope, DebugScopeType.Global, "add"));
});
Expand Down Expand Up @@ -327,7 +328,7 @@ function add(a, b)
Assert.Collection(info.CurrentScopeChain,
scope => AssertScope(scope, DebugScopeType.Block, "y"),
scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
scope => AssertScope(scope, DebugScopeType.Script, "x", "z"), // y shadowed
scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"), // y is shadowed, but still in the scope
scope => AssertScope(scope, DebugScopeType.Global, "add"));
});
}
Expand Down Expand Up @@ -391,7 +392,7 @@ function add(a, b)
scope => AssertScope(scope, DebugScopeType.Block, "x"),
scope => AssertScope(scope, DebugScopeType.Block, "y"),
scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
scope => AssertScope(scope, DebugScopeType.Script, "z"), // x, y shadowed
scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"), // x, y are shadowed, but still in the scope
scope => AssertScope(scope, DebugScopeType.Global, "add"));
});
}
Expand Down
49 changes: 48 additions & 1 deletion Jint.Tests/Runtime/Debugger/StepModeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ public void StepNotTriggeredWhenRunning()
function test()
{
'dummy';
'øummy';
'dummy';
}";

var engine = new Engine(options => options
Expand All @@ -432,5 +432,52 @@ function test()

Assert.Equal(1, stepCount);
}

[Fact]
public void SkipIsTriggeredWhenRunning()
{
string script = @"
'step';
'skip';
'skip';
debugger;
'step';
'step';
";

var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script)
.InitialStepMode(StepMode.Into));

int stepCount = 0;
int skipCount = 0;

engine.DebugHandler.Step += (sender, info) =>
{
Assert.True(TestHelpers.IsLiteral(info.CurrentNode, "step"));
stepCount++;
// Start running after first step
return stepCount == 1 ? StepMode.None : StepMode.Into;
};

engine.DebugHandler.Skip += (sender, info) =>
{
Assert.True(TestHelpers.IsLiteral(info.CurrentNode, "skip"));
skipCount++;
return StepMode.None;
};

engine.DebugHandler.Break += (sender, info) =>
{
// Back to stepping after debugger statement
return StepMode.Into;
};

engine.Execute(script);

Assert.Equal(2, skipCount);
Assert.Equal(3, stepCount);
}
}
}
138 changes: 134 additions & 4 deletions Jint.Tests/Runtime/EngineTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System.Globalization;
using System.Reflection;
using Esprima;
using Esprima.Ast;
using Jint.Native;
using Jint.Native.Array;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Debugger;
using Jint.Tests.Runtime.Debugger;
using Xunit.Abstractions;

#pragma warning disable 618
Expand Down Expand Up @@ -1584,13 +1586,11 @@ private StepMode EngineStepVerifyDebugInfo(object sender, DebugInformation debug
Assert.NotNull(debugInfo.CallStack);
Assert.NotNull(debugInfo.CurrentNode);
Assert.NotNull(debugInfo.CurrentScopeChain);
Assert.NotNull(debugInfo.CurrentScopeChain.Global);
Assert.NotNull(debugInfo.CurrentScopeChain.Local);

Assert.Equal(2, debugInfo.CallStack.Count);
Assert.Equal("func1", debugInfo.CurrentCallFrame.FunctionName);
var globalScope = debugInfo.CurrentScopeChain.Global;
var localScope = debugInfo.CurrentScopeChain.Local;
var globalScope = debugInfo.CurrentScopeChain.Single(s => s.ScopeType == DebugScopeType.Global);
var localScope = debugInfo.CurrentScopeChain.Single(s => s.ScopeType == DebugScopeType.Local);
Assert.Contains("global", globalScope.BindingNames);
Assert.Equal(true, globalScope.GetBindingValue("global").AsBoolean());
Assert.Contains("local", localScope.BindingNames);
Expand Down Expand Up @@ -2882,6 +2882,136 @@ public void ShouldAllowOptionalChainingForMemberCall()
Assert.True(array[1].IsUndefined());
}

[Fact]
public void ExecuteShouldTriggerBeforeEvaluateEvent()
{
TestBeforeEvaluateEvent(
(engine, code) => engine.Execute(code),
expectedSource: "<anonymous>"
);
}

[Fact]
public void ExecuteWithSourceShouldTriggerBeforeEvaluateEvent()
{
TestBeforeEvaluateEvent(
(engine, code) => engine.Execute(code, "mysource"),
expectedSource: "mysource"
);
}

[Fact]
public void ExecuteWithParserOptionsShouldTriggerBeforeEvaluateEvent()
{
TestBeforeEvaluateEvent(
(engine, code) => engine.Execute(code, ParserOptions.Default),
expectedSource: "<anonymous>"
);
}

[Fact]
public void ExecuteWithSourceAndParserOptionsShouldTriggerBeforeEvaluateEvent()
{
TestBeforeEvaluateEvent(
(engine, code) => engine.Execute(code, "mysource", ParserOptions.Default),
expectedSource: "mysource"
);
}

[Fact]
public void EvaluateShouldTriggerBeforeEvaluateEvent()
{
TestBeforeEvaluateEvent(
(engine, code) => engine.Evaluate(code),
expectedSource: "<anonymous>"
);
}

[Fact]
public void EvaluateWithSourceShouldTriggerBeforeEvaluateEvent()
{
TestBeforeEvaluateEvent(
(engine, code) => engine.Evaluate(code, "mysource"),
expectedSource: "mysource"
);
}

[Fact]
public void EvaluateWithParserOptionsShouldTriggerBeforeEvaluateEvent()
{
TestBeforeEvaluateEvent(
(engine, code) => engine.Evaluate(code, ParserOptions.Default),
expectedSource: "<anonymous>"
);
}

[Fact]
public void EvaluateWithSourceAndParserOptionsShouldTriggerBeforeEvaluateEvent()
{
TestBeforeEvaluateEvent(
(engine, code) => engine.Evaluate(code, "mysource", ParserOptions.Default),
expectedSource: "mysource"
);
}

[Fact]
public void ImportModuleShouldTriggerBeforeEvaluateEvents()
{
var engine = new Engine();

const string module1 = "import dummy from 'module2';";
const string module2 = "export default 'dummy';";

var beforeEvaluateTriggeredCount = 0;
engine.DebugHandler.BeforeEvaluate += (sender, ast) =>
{
beforeEvaluateTriggeredCount++;
Assert.Equal(engine, sender);
switch (beforeEvaluateTriggeredCount)
{
case 1:
Assert.Equal("module1", ast.Location.Source);
Assert.Collection(ast.Body,
node => Assert.IsType<ImportDeclaration>(node)
);
break;
case 2:
Assert.Equal("module2", ast.Location.Source);
Assert.Collection(ast.Body,
node => Assert.IsType<ExportDefaultDeclaration>(node)
);
break;
}
};

engine.AddModule("module1", module1);
engine.AddModule("module2", module2);
engine.ImportModule("module1");

Assert.Equal(2, beforeEvaluateTriggeredCount);
}

private static void TestBeforeEvaluateEvent(Action<Engine, string> call, string expectedSource)
{
var engine = new Engine();

const string script = "'dummy';";

var beforeEvaluateTriggered = false;
engine.DebugHandler.BeforeEvaluate += (sender, ast) =>
{
beforeEvaluateTriggered = true;
Assert.Equal(engine, sender);
Assert.Equal(expectedSource, ast.Location.Source);
Assert.Collection(ast.Body, node => Assert.True(TestHelpers.IsLiteral(node, "dummy")));
};

call(engine, script);

Assert.True(beforeEvaluateTriggered);
}

private class Wrapper
{
public Testificate Test { get; set; }
Expand Down
17 changes: 0 additions & 17 deletions Jint.Tests/Runtime/Modules/DefaultModuleLoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,6 @@ public void ShouldRejectPathsOutsideOfBasePath(string specifier)
Assert.StartsWith(exc.Specifier, specifier);
}

[Fact]
public void ShouldTriggerLoadedEvent()
{
var loader = new DefaultModuleLoader(ModuleTests.GetBasePath());
bool triggered = false;
loader.Loaded += (sender, source, module) =>
{
Assert.Equal(loader, sender);
Assert.NotNull(source);
Assert.NotNull(module);
triggered = true;
};
var engine = new Engine(options => options.EnableModules(loader));
engine.ImportModule("./modules/format-name.js");
Assert.True(triggered);
}

[Fact]
public void ShouldResolveBareSpecifiers()
{
Expand Down
5 changes: 5 additions & 0 deletions Jint/Engine.Modules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ internal ModuleRecord LoadModule(string? referencingModuleLocation, string speci
module = LoaderFromModuleLoader(moduleResolution);
}

if (module is SourceTextModuleRecord sourceTextModule)
{
DebugHandler.OnBeforeEvaluate(sourceTextModule._source);
}

return module;
}

Expand Down
4 changes: 3 additions & 1 deletion Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ public Engine Execute(Script script)
/// </summary>
private Engine ScriptEvaluation(ScriptRecord scriptRecord)
{
DebugHandler.OnBeforeEvaluate(scriptRecord.EcmaScriptCode);

var globalEnv = Realm.GlobalEnv;

var scriptContext = new ExecutionContext(
Expand Down Expand Up @@ -363,7 +365,7 @@ public ManualPromise RegisterPromise()

Action<JsValue> SettleWith(FunctionInstance settle) => value =>
{
settle.Call(JsValue.Undefined, new[] {value});
settle.Call(JsValue.Undefined, new[] { value });
RunAvailableContinuations();
};

Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/Debugger/BreakLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public BreakLocation(int line, int column) : this(null, line, column)

}

public BreakLocation(string source, Esprima.Position position) : this(source, position.Line, position.Column)
public BreakLocation(string? source, Esprima.Position position) : this(source, position.Line, position.Column)
{
}

Expand Down

0 comments on commit 3c45b8c

Please sign in to comment.