* Adding stack info to the exception details when evaluating expressions.
* passing only one parameter.
* Fixing CI.
* Fixing format.
* Added a new test: Evaluate on a callframe different of the first and check if the callstack is correct.
And fix it.
* Addressing @radical comments.
* addressing @radical comments.
{
get
{
- _error.Value["exceptionDetails"]["stackTrace"] = StackTrace;
return _error;
}
set { }
result = result,
exceptionDetails = new
{
- exception = result,
- stackTrace = StackTrace
+ exception = result
}
}));
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#nullable enable
-
-using System;
-
-namespace Microsoft.WebAssembly.Diagnostics;
-
-public class ExpressionEvaluationFailedException : Exception
-{
- public ExpressionEvaluationFailedException(string message) : base(message)
- {
- }
-
- public ExpressionEvaluationFailedException(string? message, Exception? innerException) : base(message, innerException)
- {
- }
-}
}
catch (Exception ex)
{
- throw new ExpressionEvaluationFailedException($"BUG: Unable to get properties for scope: {scopeId}. {ex}");
+ throw new ReturnAsErrorException($"BUG: Unable to get properties for scope: {scopeId}. {ex}", ex.GetType().Name);
}
localsFetched = true;
}
throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'");
}
}
- catch (Exception ex) when (ex is not ExpressionEvaluationFailedException)
+ catch (Exception ex)
{
- throw new ExpressionEvaluationFailedException($"Unable to evaluate element access '{elementAccess}': {ex.Message}", ex);
+ throw new ReturnAsErrorException($"Unable to evaluate element access '{elementAccess}': {ex.Message}", ex.GetType().Name);
}
async Task<ElementIndexInfo> GetElementIndexInfo()
methodName = memberAccessExpressionSyntax.Name.ToString();
if (rootObject.IsNullValuedObject())
- throw new ExpressionEvaluationFailedException($"Expression '{memberAccessExpressionSyntax}' evaluated to null");
+ throw new ReturnAsErrorException($"Expression '{memberAccessExpressionSyntax}' evaluated to null", "NullReferenceException");
}
else if (expr is IdentifierNameSyntax && scopeCache.ObjectFields.TryGetValue("this", out JObject thisValue))
{
}
return (rootObject, methodName);
}
- catch (Exception ex) when (ex is not (ExpressionEvaluationFailedException or ReturnAsErrorException))
+ catch (Exception ex) when (ex is not ReturnAsErrorException)
{
throw new Exception($"Unable to evaluate method '{methodName}'", ex);
}
}
throw new ReturnAsErrorException($"No implementation of method '{methodName}' matching '{method}' found in type {rootObject["className"]}.", "ArgumentError");
}
- catch (Exception ex) when (ex is not (ExpressionEvaluationFailedException or ReturnAsErrorException))
+ catch (Exception ex) when (ex is not ReturnAsErrorException)
{
- throw new ExpressionEvaluationFailedException($"Unable to evaluate method '{method}': {ex.Message}", ex);
+ throw new ReturnAsErrorException($"Unable to evaluate method '{method}': {ex.Message}", ex.GetType().Name);
}
async Task<int> FindMethodIdOnLinqEnumerable(IList<int> typeIds, string methodName)
await SendResume(sessionId, token);
}
}
+ private Result AddCallStackInfoToException(Result _error, ExecutionContext context, int scopeId)
+ {
+ try {
+ var retStackTrace = new JArray();
+ foreach(var call in context.CallStack)
+ {
+ if (call.Id < scopeId)
+ continue;
+ retStackTrace.Add(JObject.FromObject(new
+ {
+ functionName = call.Method.Name,
+ scriptId = call.Location.Id.ToString(),
+ url = context.Store.ToUrl(call.Location),
+ lineNumber = call.Location.Line,
+ columnNumber = call.Location.Column
+ }));
+ }
+ if (!_error.Value.ContainsKey("exceptionDetails"))
+ _error.Value["exceptionDetails"] = new JObject();
+ _error.Value["exceptionDetails"]["stackTrace"] = JObject.FromObject(new {callFrames = retStackTrace});
+ return _error;
+ }
+ catch (Exception e)
+ {
+ logger.LogDebug($"Unable to add stackTrace information to exception. {e}");
+ }
+ return _error;
+ }
private async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scopeId, string expression, CancellationToken token)
{
+ ExecutionContext context = GetContext(msg_id);
try
{
- ExecutionContext context = GetContext(msg_id);
if (context.CallStack == null)
return false;
}
else
{
- SendResponse(msg_id, Result.Err($"Unable to evaluate '{expression}'"), token);
+ SendResponse(msg_id, AddCallStackInfoToException(Result.Err($"Unable to evaluate '{expression}'"), context, scopeId), token);
}
}
catch (ReturnAsErrorException ree)
{
- SendResponse(msg_id, ree.Error, token);
- }
- catch (ExpressionEvaluationFailedException eefe)
- {
- logger.LogDebug($"Error in EvaluateOnCallFrame for expression '{expression}' with '{eefe}.");
- SendResponse(msg_id, Result.Exception(eefe), token);
+ SendResponse(msg_id, AddCallStackInfoToException(ree.Error, context, scopeId), token);
}
catch (Exception e)
{
logger.LogDebug($"Error in EvaluateOnCallFrame for expression '{expression}' with '{e}.");
- SendResponse(msg_id, Result.Exception(e), token);
+ var exc = new ReturnAsErrorException(e.Message, e.GetType().Name);
+ SendResponse(msg_id, AddCallStackInfoToException(exc.Error, context, scopeId), token);
}
return true;
"DebuggerTests.EvaluateMethodTestsClass.TestEvaluate", "run", 9, "DebuggerTests.EvaluateMethodTestsClass.TestEvaluate.run",
"window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.EvaluateMethodTestsClass:EvaluateMethods'); })",
wait_for_event_fn: async (pause_location) =>
- {
- var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+ {
+ var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
- var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false );
- Assert.Equal(
- $"Method 'MyMethodWrong' not found in type 'DebuggerTests.EvaluateMethodTestsClass.ParmToTest'",
- res.Error["result"]?["description"]?.Value<string>());
+ var (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethodWrong()", expect_ok: false );
+ Assert.Equal(
+ $"Method 'MyMethodWrong' not found in type 'DebuggerTests.EvaluateMethodTestsClass.ParmToTest'",
+ res.Error["result"]?["description"]?.Value<string>());
- (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false);
- Assert.Equal(
- "Unable to evaluate method 'MyMethod'. Too many arguments passed.",
- res.Error["result"]?["description"]?.Value<string>());
+ (_, res) = await EvaluateOnCallFrame(id, "this.objToTest.MyMethod(1)", expect_ok: false);
+ Assert.Equal(
+ "Unable to evaluate method 'MyMethod'. Too many arguments passed.",
+ res.Error["result"]?["description"]?.Value<string>());
- (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false );
- Assert.Contains("No implementation of method 'CallMethodWithParm' matching 'this.CallMethodWithParm(\"1\")' found in type DebuggerTests.EvaluateMethodTestsClass.TestEvaluate.", res.Error["result"]?["description"]?.Value<string>());
+ (_, res) = await EvaluateOnCallFrame(id, "this.CallMethodWithParm(\"1\")", expect_ok: false );
+ Assert.Contains("No implementation of method 'CallMethodWithParm' matching 'this.CallMethodWithParm(\"1\")' found in type DebuggerTests.EvaluateMethodTestsClass.TestEvaluate.", res.Error["result"]?["description"]?.Value<string>());
- (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false );
- Assert.Equal("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["message"]?.Value<string>());
+ (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjNull.MyMethod()", expect_ok: false );
+ Assert.Equal("Expression 'this.ParmToTestObjNull.MyMethod' evaluated to null", res.Error["result"]?["description"]?.Value<string>());
+ var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
+ Assert.Equal("DebuggerTests.EvaluateMethodTestsClass.TestEvaluate.run", exceptionDetailsStack?["functionName"]?.Value<string>());
+ Assert.Equal(358, exceptionDetailsStack?["lineNumber"]?.Value<int>());
+ Assert.Equal(16, exceptionDetailsStack?["columnNumber"]?.Value<int>());;
- (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false );
- Assert.Equal("Method 'MyMethod' not found in type 'string'", res.Error["result"]?["description"]?.Value<string>());
- });
+ (_, res) = await EvaluateOnCallFrame(id, "this.ParmToTestObjException.MyMethod()", expect_ok: false );
+ Assert.Equal("Method 'MyMethod' not found in type 'string'", res.Error["result"]?["description"]?.Value<string>());
+ });
[Fact]
public async Task EvaluateSimpleMethodCallsWithoutParms() => await CheckInspectLocalsAtBreakpointSite(
{
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
var (_, res) = await EvaluateOnCallFrame(id, "f.idx0[2]", expect_ok: false );
- Assert.Equal("Unable to evaluate element access 'f.idx0[2]': Cannot apply indexing with [] to a primitive object of type 'number'", res.Error["message"]?.Value<string>());
+ Assert.Equal("Unable to evaluate element access 'f.idx0[2]': Cannot apply indexing with [] to a primitive object of type 'number'", res.Error["result"]?["description"]?.Value<string>());
+ var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
+ Assert.Equal("DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", exceptionDetailsStack?["functionName"]?.Value<string>());
+ Assert.Equal(556, exceptionDetailsStack?["lineNumber"]?.Value<int>());
+ Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value<int>());
(_, res) = await EvaluateOnCallFrame(id, "f[1]", expect_ok: false );
- Assert.Equal( "Unable to evaluate element access 'f[1]': Cannot apply indexing with [] to an object of type 'DebuggerTests.EvaluateLocalsWithIndexingTests.TestEvaluate'", res.Error["message"]?.Value<string>());
+ Assert.Equal( "Unable to evaluate element access 'f[1]': Cannot apply indexing with [] to an object of type 'DebuggerTests.EvaluateLocalsWithIndexingTests.TestEvaluate'", res.Error["result"]?["description"]?.Value<string>());
});
[Fact]
// indexing with expression of a wrong type
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
var (_, res) = await EvaluateOnCallFrame(id, "f.numList[\"a\" + 1]", expect_ok: false );
- Assert.Equal("Unable to evaluate element access 'f.numList[\"a\" + 1]': Cannot index with an object of type 'string'", res.Error["message"]?.Value<string>());
+ Assert.Equal("Unable to evaluate element access 'f.numList[\"a\" + 1]': Cannot index with an object of type 'string'", res.Error["result"]?["description"]?.Value<string>());
+ var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
+ Assert.Equal("DebuggerTests.EvaluateLocalsWithIndexingTests.EvaluateLocals", exceptionDetailsStack?["functionName"]?.Value<string>());
+ Assert.Equal(556, exceptionDetailsStack?["lineNumber"]?.Value<int>());
+ Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value<int>());
});
[ConditionalFact(nameof(RunningOnChrome))]
wait_for_event_fn: async (pause_location) =>
{
var id = pause_location["callFrames"][0]["callFrameId"].Value<string>();
+ var id_prev = pause_location["callFrames"][1]["callFrameId"].Value<string>();
var (_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.EvaluateStaticFieldsInStaticClass.StaticProperty2", expect_ok: false);
AssertEqual("Failed to resolve member access for DebuggerTests.EvaluateStaticFieldsInStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value<string>(), "wrong error message");
-
+ var exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
+ Assert.Equal("DebuggerTests.EvaluateMethodTestsClass.TestEvaluate.run", exceptionDetailsStack?["functionName"]?.Value<string>());
+ Assert.Equal(358, exceptionDetailsStack?["lineNumber"]?.Value<int>());
+ Assert.Equal(16, exceptionDetailsStack?["columnNumber"]?.Value<int>());
+ (_, res) = await EvaluateOnCallFrame(id_prev, "DebuggerTests.EvaluateStaticFieldsInStaticClass.StaticProperty2", expect_ok: false);
+ exceptionDetailsStack = res.Error["exceptionDetails"]?["stackTrace"]?["callFrames"]?[0];
+ AssertEqual("Failed to resolve member access for DebuggerTests.EvaluateStaticFieldsInStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value<string>(), "wrong error message");
+ Assert.Equal("DebuggerTests.EvaluateMethodTestsClass.EvaluateMethods", exceptionDetailsStack?["functionName"]?.Value<string>());
+ Assert.Equal(422, exceptionDetailsStack?["lineNumber"]?.Value<int>());
+ Assert.Equal(12, exceptionDetailsStack?["columnNumber"]?.Value<int>());
(_, res) = await EvaluateOnCallFrame(id, "DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", expect_ok: false);
AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value<string>(), "wrong error message");
});