Skip to content

Commit 511c1cd

Browse files
committed
Implementation of JS stack trace
1 parent e94a864 commit 511c1cd

9 files changed

Lines changed: 164 additions & 46 deletions

File tree

NiL.JS/Core/CallStackMarker.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace NiL.JS.Core
2+
{
3+
internal readonly struct CallStackMarker
4+
{
5+
public int Index { get; }
6+
7+
public CallStackMarker(int index)
8+
{
9+
Index = index;
10+
}
11+
12+
public override bool Equals(object obj)
13+
{
14+
return obj is CallStackMarker marker &&
15+
Index == marker.Index;
16+
}
17+
18+
public override int GetHashCode()
19+
{
20+
return -2134847229 + Index.GetHashCode();
21+
}
22+
}
23+
}

NiL.JS/Core/Context.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public static Context CurrentContext
9898
internal JSValue _thisBind;
9999
internal Function _owner;
100100
internal Context _parent;
101+
internal string _sourceCode;
101102
internal IDictionary<string, JSValue> _variables;
102103
internal bool _strict;
103104
internal VariableDescriptor[] _definedVariables;
@@ -630,6 +631,7 @@ public JSValue Eval(string code, JSValue thisBind, bool suppressScopeCreation =
630631
var oldThisBind = ThisBind;
631632
var runContextOfEval = context.Activate();
632633
context._thisBind = thisBind;
634+
context._sourceCode = code;
633635
try
634636
{
635637
return body.Evaluate(context) ?? context._lastResult ?? JSValue.notExists;

NiL.JS/Core/Functions/MethodProxy.cs

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ private RestPrmsConverter makeRestPrmsArrayCreator()
221221
};
222222

223223
var lambda = Expression.Lambda<RestPrmsConverter>(
224-
Expression.Block(new[] { argumentIndex, resultArray, resultArrayIndex, tempValue }, tree),
224+
Expression.Block(new[] { argumentIndex, resultArray, resultArrayIndex, tempValue }, tree),
225225
context, arguments, argumentsObjectPrm);
226-
226+
227227
return lambda.Compile();
228228
}
229229

@@ -480,30 +480,16 @@ private object invokeMethod(JSValue targetValue, Expressions.Expression[] argume
480480
{
481481
object value;
482482
var target = GetTargetObject(targetValue, _hardTarget);
483-
try
483+
if (_parameters.Length == 0 && argumentsSource != null)
484484
{
485-
if (_parameters.Length == 0 && argumentsSource != null)
486-
{
487-
for (var i = 0; i < argumentsSource.Length; i++)
488-
argumentsSource[i].Evaluate(initiator);
489-
}
490-
491-
value = _fastWrapper(target, initiator, argumentsSource, argumentsObject);
492-
493-
if (_returnConverter != null)
494-
value = _returnConverter.From(value);
485+
for (var i = 0; i < argumentsSource.Length; i++)
486+
argumentsSource[i].Evaluate(initiator);
495487
}
496-
catch (Exception e)
497-
{
498-
while (e.InnerException != null)
499-
e = e.InnerException;
500488

501-
if (e is JSException)
502-
throw e;
489+
value = _fastWrapper(target, initiator, argumentsSource, argumentsObject);
503490

504-
ExceptionHelper.Throw(new TypeError(e.Message), e);
505-
throw;
506-
}
491+
if (_returnConverter != null)
492+
value = _returnConverter.From(value);
507493

508494
return value;
509495
}

NiL.JS/Core/JSException.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace NiL.JS.Core
1212
#endif
1313
public sealed class JSException : Exception
1414
{
15+
private string _stackTraceOverride;
16+
1517
public JSValue Error { get; }
1618
public CodeNode ExceptionMaker { get; }
1719
public string Code { get; internal set; }
@@ -49,6 +51,8 @@ public JSException(Error avatar, Exception innerException)
4951
Error = Context.CurrentGlobalContext.ProxyValue(avatar);
5052
}
5153

54+
public override string StackTrace => _stackTraceOverride ?? base.StackTrace;
55+
5256
public override string Message
5357
{
5458
get
@@ -74,5 +78,10 @@ public override string Message
7478
return result;
7579
}
7680
}
81+
82+
internal void InternalSetStackTrace(string stackTrace)
83+
{
84+
_stackTraceOverride = stackTrace;
85+
}
7786
}
7887
}

NiL.JS/ExceptionHelper.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ internal static void ThrowVariableIsNotDefined(string variableName, CodeNode exc
7676
Throw(new ReferenceError(string.Format(Strings.VariableNotDefined, variableName)), exceptionMaker, code);
7777
}
7878

79-
private static string GetCode(Context context)
79+
internal static string GetCode(Context context)
8080
{
81-
var code = context.RootContext._owner?._functionDefinition?._body?.Code;
82-
if (code == null)
83-
code = context._module?.Script.Code;
84-
return code;
81+
while (context != null && context._sourceCode == null)
82+
context = context._parent;
83+
84+
return context?._sourceCode ?? Script.CurrentScript?.Code;
8585
}
8686

8787
/// <exception cref="NiL.JS.Core.JSException">

NiL.JS/Expressions/Call.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,32 @@ public override JSValue Evaluate(Context context)
168168
if (_callMode == CallMode.Construct)
169169
targetObject = null;
170170

171-
if ((function._attributes & JSValueAttributesInternal.Eval) != 0)
172-
return callEval(context);
171+
try
172+
{
173+
if ((function._attributes & JSValueAttributesInternal.Eval) != 0)
174+
return callEval(context);
175+
176+
return func.InternalInvoke(targetObject, _arguments, context, _withSpread, _callMode != 0);
177+
}
178+
catch (Exception e)
179+
{
180+
foreach(var item in e.Data.Values)
181+
{
182+
if ((item as Tuple<Context, CodeCoordinates>).Item1 == context)
183+
throw;
184+
}
173185

174-
return func.InternalInvoke(targetObject, _arguments, context, _withSpread, _callMode != 0);
186+
e.Data.Add(
187+
new CallStackMarker(e.Data.Count),
188+
Tuple.Create(
189+
context,
190+
CodeCoordinates.FromTextPosition(
191+
ExceptionHelper.GetCode(context),
192+
Position,
193+
Length)));
194+
195+
throw;
196+
}
175197
}
176198

177199
private JSValue callEval(Context context)

NiL.JS/Script.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ namespace NiL.JS
77
{
88
public sealed class Script
99
{
10+
[ThreadStatic]
11+
private static readonly Stack<Script> _scriptsStack = new Stack<Script>();
12+
13+
internal static Script CurrentScript => _scriptsStack.Count > 0 ? _scriptsStack.Peek() : null;
14+
1015
public string Code { get; private set; }
1116
public CodeBlock Root { get; private set; }
1217

@@ -28,7 +33,7 @@ public static Script Parse(string code, CompilerMessageCallback messageCallback
2833
Code = ""
2934
};
3035
}
31-
36+
3237
var internalCallback = messageCallback != null ?
3338
(level, position, length, message) => messageCallback(level, CodeCoordinates.FromTextPosition(code, position, length), message)
3439
: null as InternalCompilerMessageCallback;
@@ -62,6 +67,9 @@ public JSValue Evaluate(Context context)
6267
if (Code == "")
6368
return JSValue.Undefined;
6469

70+
lock (_scriptsStack)
71+
_scriptsStack.Push(this);
72+
6573
try
6674
{
6775
context.Activate();
@@ -72,6 +80,9 @@ public JSValue Evaluate(Context context)
7280
for (var i = 0; i < Root._variables.Length; i++)
7381
Root._variables[i].cacheContext = null;
7482
context.Deactivate();
83+
84+
lock (_scriptsStack)
85+
_scriptsStack.Pop();
7586
}
7687
}
7788
}

NiL.JS/Statements/CodeBlock.cs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -345,13 +345,16 @@ private void evaluateWithScope(Context context, int i, bool clearSuspendData)
345345

346346
private void evaluateLines(Context context, int i, bool clearSuspendData)
347347
{
348-
for (var ls = _lines; i < ls.Length; i++)
348+
var ls = _lines;
349+
try
349350
{
350-
if (context._debugging)
351-
context.raiseDebugger(_lines[i]);
352-
var t = ls[i].Evaluate(context);
353-
if (t != null)
354-
context._lastResult = t;
351+
for (; i < ls.Length; i++)
352+
{
353+
if (context._debugging)
354+
context.raiseDebugger(_lines[i]);
355+
var t = ls[i].Evaluate(context);
356+
if (t != null)
357+
context._lastResult = t;
355358
#if DEBUG && !(PORTABLE || NETCORE)
356359
if (!context.Running)
357360
if (System.Diagnostics.Debugger.IsAttached)
@@ -388,18 +391,38 @@ private void evaluateLines(Context context, int i, bool clearSuspendData)
388391
else
389392
throw new ApplicationException("Boolean.True has been rewitten");
390393
#endif
391-
if (context._executionMode != ExecutionMode.Regular)
392-
{
393-
if (context._executionMode == ExecutionMode.Suspend)
394+
if (context._executionMode != ExecutionMode.Regular)
394395
{
395-
context.SuspendData[this] = new SuspendData { Context = context, LineIndex = i };
396+
if (context._executionMode == ExecutionMode.Suspend)
397+
{
398+
context.SuspendData[this] = new SuspendData { Context = context, LineIndex = i };
399+
}
400+
401+
break;
396402
}
397403

398-
break;
404+
if (clearSuspendData)
405+
context.SuspendData.Clear();
406+
}
407+
}
408+
catch (Exception e)
409+
{
410+
foreach (var item in e.Data.Values)
411+
{
412+
if ((item as Tuple<Context, CodeCoordinates>).Item1 == context)
413+
throw;
399414
}
400415

401-
if (clearSuspendData)
402-
context.SuspendData.Clear();
416+
e.Data.Add(
417+
new CallStackMarker(e.Data.Count),
418+
Tuple.Create(
419+
context,
420+
CodeCoordinates.FromTextPosition(
421+
ExceptionHelper.GetCode(context),
422+
ls[i].Position,
423+
ls[i].Length)));
424+
425+
throw;
403426
}
404427
}
405428

NiL.JS/Statements/TryCatch.cs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Reflection;
34
using System.Runtime.CompilerServices;
45
using NiL.JS.BaseLibrary;
56
using NiL.JS.Core;
6-
using NiL.JS.Core.Interop;
77
using NiL.JS.Expressions;
88

99
namespace NiL.JS.Statements
@@ -128,7 +128,49 @@ public override JSValue Evaluate(Context context)
128128
}
129129
catch (Exception e)
130130
{
131-
if (this._catch)
131+
var stackTrace = new List<string>();
132+
133+
var innerEx = e;
134+
while (innerEx != null)
135+
{
136+
var isOurException = false;
137+
for (var i = innerEx.Data.Count; i-- > 0;)
138+
{
139+
var item = innerEx.Data[new CallStackMarker(i)] as Tuple<Context, CodeCoordinates>;
140+
if (item == null)
141+
continue;
142+
143+
isOurException = true;
144+
stackTrace.Add(" at " + (item.Item1?._owner?.name ?? "<unknown function>") + ": line " + item.Item2.Line);
145+
}
146+
147+
if (!isOurException)
148+
stackTrace.Add(innerEx.StackTrace);
149+
150+
innerEx = innerEx.InnerException;
151+
}
152+
153+
if (e is TargetInvocationException targetInvocationException)
154+
{
155+
var baseException = targetInvocationException.GetBaseException();
156+
if (baseException is JSException jsEx)
157+
{
158+
e = baseException;
159+
}
160+
else
161+
{
162+
jsEx = new JSException(new TypeError(e.Message), e);
163+
e = jsEx;
164+
}
165+
166+
if (stackTrace.Count > 0)
167+
{
168+
stackTrace.Reverse();
169+
jsEx.InternalSetStackTrace(string.Join(Environment.NewLine, stackTrace));
170+
}
171+
}
172+
173+
if (_catch)
132174
{
133175
if (catchBody != null)
134176
catchHandler(context, e);

0 commit comments

Comments
 (0)