Skip to content

Commit 48ccbf4

Browse files
committed
In Classic mode during debugging now script error contains a full stack trace
1 parent 81a6ca8 commit 48ccbf4

13 files changed

+370
-8
lines changed

NuGet/MsieJavaScriptEngine.nuspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
<requireLicenseAcceptance>false</requireLicenseAcceptance>
1313
<description>This library is a .NET wrapper for working with the JavaScript engines of Internet Explorer and Edge (JsRT versions of Chakra, ActiveScript version of Chakra and Classic JavaScript Engine). Project was based on the code of SassAndCoffee.JavaScript (http://github.com/paulcbetts/SassAndCoffee), Chakra Sample Hosts (http://github.com/panopticoncentral/chakra-host) and jsrt-dotnet (http://github.com/robpaveza/jsrt-dotnet).</description>
1414
<summary>This library is a .NET wrapper for working with the JavaScript engines of Internet Explorer and Edge (JsRT versions of Chakra, ActiveScript version of Chakra and Classic JavaScript Engine).</summary>
15-
<releaseNotes>In ActiveScript modes now are uses the short names of error categories.</releaseNotes>
15+
<releaseNotes>1. In ActiveScript modes now are uses the short names of error categories;
16+
2. In `Classic` mode during debugging now script error contains a full stack trace.</releaseNotes>
1617
<copyright>Copyright (c) 2012-2017 Andrey Taritsyn - http://www.taritsyn.ru</copyright>
1718
<language>en-US</language>
1819
<tags>JavaScript ECMAScript MSIE IE Edge Chakra</tags>

NuGet/readme.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
=============
2222
RELEASE NOTES
2323
=============
24-
In ActiveScript modes now are uses the short names of error categories.
24+
1. In ActiveScript modes now are uses the short names of error categories;
25+
2. In `Classic` mode during debugging now script error contains a full stack
26+
trace.
2527

2628
============
2729
PROJECT SITE

src/MsieJavaScriptEngine.Net4/MsieJavaScriptEngine.Net40.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@
165165
<Compile Include="..\MsieJavaScriptEngine\ActiveScript\Debugging\IDebugStackFrameSniffer.cs">
166166
<Link>ActiveScript\Debugging\IDebugStackFrameSniffer.cs</Link>
167167
</Compile>
168+
<Compile Include="..\MsieJavaScriptEngine\ActiveScript\Debugging\IDebugStackFrameSnifferEx32.cs">
169+
<Link>ActiveScript\Debugging\IDebugStackFrameSnifferEx32.cs</Link>
170+
</Compile>
171+
<Compile Include="..\MsieJavaScriptEngine\ActiveScript\Debugging\IDebugStackFrameSnifferEx64.cs">
172+
<Link>ActiveScript\Debugging\IDebugStackFrameSnifferEx64.cs</Link>
173+
</Compile>
168174
<Compile Include="..\MsieJavaScriptEngine\ActiveScript\Debugging\IDebugSyncOperation.cs">
169175
<Link>ActiveScript\Debugging\IDebugSyncOperation.cs</Link>
170176
</Compile>
@@ -201,6 +207,9 @@
201207
<Compile Include="..\MsieJavaScriptEngine\ActiveScript\Debugging\IRemoteDebugApplicationThread.cs">
202208
<Link>ActiveScript\Debugging\IRemoteDebugApplicationThread.cs</Link>
203209
</Compile>
210+
<Compile Include="..\MsieJavaScriptEngine\ActiveScript\Debugging\NullEnumDebugStackFrames.cs">
211+
<Link>ActiveScript\Debugging\NullEnumDebugStackFrames.cs</Link>
212+
</Compile>
204213
<Compile Include="..\MsieJavaScriptEngine\ActiveScript\Debugging\ProcessDebugManager.cs">
205214
<Link>ActiveScript\Debugging\ProcessDebugManager.cs</Link>
206215
</Compile>
@@ -495,6 +504,9 @@
495504
<Compile Include="..\MsieJavaScriptEngine\Undefined.cs">
496505
<Link>Undefined.cs</Link>
497506
</Compile>
507+
<Compile Include="..\MsieJavaScriptEngine\Utilities\CharExtensions.cs">
508+
<Link>Utilities\CharExtensions.cs</Link>
509+
</Compile>
498510
<Compile Include="..\MsieJavaScriptEngine\Utilities\StringBuilderExtensions.cs">
499511
<Link>Utilities\StringBuilderExtensions.cs</Link>
500512
</Compile>

src/MsieJavaScriptEngine/ActiveScript/ActiveScriptJsEngineBase.ScriptSite.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
using System;
33
using System.Globalization;
44
using System.Runtime.InteropServices;
5+
using System.Text;
56

67
using EXCEPINFO = System.Runtime.InteropServices.ComTypes.EXCEPINFO;
78

89
using MsieJavaScriptEngine.ActiveScript.Debugging;
910
using MsieJavaScriptEngine.Constants;
11+
using MsieJavaScriptEngine.Helpers;
1012
using MsieJavaScriptEngine.Resources;
1113

1214
namespace MsieJavaScriptEngine.ActiveScript
@@ -44,15 +46,44 @@ private string GetErrorDetails(IActiveScriptError error)
4446
EXCEPINFO excepInfo;
4547
error.GetExceptionInfo(out excepInfo);
4648

47-
string errorDetails = string.Format("{0}: {1}",
49+
string errorDetails;
50+
string message = string.Format("{0}: {1}",
4851
_jsEngine.ShortenErrorCategoryName(excepInfo.bstrSource), excepInfo.bstrDescription);
52+
4953
if (_jsEngine._processDebugManagerWrapper != null)
5054
{
51-
string errorLocation = GetErrorLocation(error);
52-
if (!string.IsNullOrWhiteSpace(errorLocation))
55+
bool isSyntaxError = false;
56+
57+
if (ComHelpers.HResult.GetFacility(excepInfo.scode) == ComHelpers.HResult.FACILITY_CONTROL)
58+
{
59+
int errorCode = ComHelpers.HResult.GetCode(excepInfo.scode);
60+
isSyntaxError = errorCode >= 1002 && errorCode <= 1035;
61+
}
62+
63+
var errorBuilder = new StringBuilder(message);
64+
string stackTrace = !isSyntaxError ? _jsEngine.GetStackTrace() : string.Empty;
65+
66+
if (!string.IsNullOrWhiteSpace(stackTrace))
5367
{
54-
errorDetails += Environment.NewLine + errorLocation;
68+
errorBuilder.AppendLine();
69+
errorBuilder.Append(stackTrace);
5570
}
71+
else
72+
{
73+
string errorLocation = GetErrorLocation(error);
74+
if (!string.IsNullOrWhiteSpace(errorLocation))
75+
{
76+
errorBuilder.AppendLine();
77+
errorBuilder.Append(errorLocation);
78+
}
79+
}
80+
81+
errorDetails = errorBuilder.ToString();
82+
errorBuilder.Clear();
83+
}
84+
else
85+
{
86+
errorDetails = message;
5687
}
5788

5889
return errorDetails;

src/MsieJavaScriptEngine/ActiveScript/ActiveScriptJsEngineBase.cs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Reflection;
66
using System.Runtime.InteropServices;
77
using System.Runtime.InteropServices.Expando;
8+
using System.Text;
89

910
using MsieJavaScriptEngine.ActiveScript.Debugging;
1011
using MsieJavaScriptEngine.Constants;
@@ -88,7 +89,7 @@ internal abstract partial class ActiveScriptJsEngineBase : InnerJsEngineBase
8889
/// <summary>
8990
/// Prefix of error category name
9091
/// </summary>
91-
private string _errorCategoryNamePrefix;
92+
private readonly string _errorCategoryNamePrefix;
9293

9394

9495
/// <summary>
@@ -364,6 +365,100 @@ private void ThrowError()
364365
}
365366
}
366367

368+
/// <summary>
369+
/// Gets a string representation of the script call stack
370+
/// </summary>
371+
/// <returns>The script call stack formatted as a string</returns>
372+
private string GetStackTrace()
373+
{
374+
StringBuilder stackTraceBuilder = null;
375+
376+
IEnumDebugStackFrames enumFrames;
377+
_activeScriptWrapper.EnumStackFrames(out enumFrames);
378+
379+
while (true)
380+
{
381+
DebugStackFrameDescriptor descriptor;
382+
uint countFetched;
383+
enumFrames.Next(1, out descriptor, out countFetched);
384+
if (countFetched < 1)
385+
{
386+
break;
387+
}
388+
389+
if (stackTraceBuilder == null)
390+
{
391+
stackTraceBuilder = new StringBuilder();
392+
}
393+
394+
try
395+
{
396+
IDebugStackFrame stackFrame = descriptor.Frame;
397+
398+
string description;
399+
stackFrame.GetDescriptionString(true, out description);
400+
401+
if (string.Equals(description, "JScript global code", StringComparison.Ordinal))
402+
{
403+
description = "Global code";
404+
}
405+
406+
IDebugCodeContext codeContext;
407+
stackFrame.GetCodeContext(out codeContext);
408+
409+
IDebugDocumentContext documentContext;
410+
codeContext.GetDocumentContext(out documentContext);
411+
412+
if (documentContext == null)
413+
{
414+
stackTraceBuilder.AppendFormatLine(" at {0}", description);
415+
}
416+
else
417+
{
418+
IDebugDocument document;
419+
documentContext.GetDocument(out document);
420+
421+
string documentName;
422+
document.GetName(DocumentNameType.Title, out documentName);
423+
424+
var documentText = (IDebugDocumentText)document;
425+
426+
uint position;
427+
uint length;
428+
documentText.GetPositionOfContext(documentContext, out position, out length);
429+
430+
uint lineNumber;
431+
uint offsetInLine;
432+
documentText.GetLineOfPosition(position, out lineNumber, out offsetInLine);
433+
uint columnNumber = offsetInLine + 1;
434+
435+
stackTraceBuilder.AppendFormatLine(" at {0} ({1}:{2}:{3})", description, documentName,
436+
lineNumber, columnNumber);
437+
}
438+
}
439+
finally
440+
{
441+
if (descriptor.pFinalObject != IntPtr.Zero)
442+
{
443+
Marshal.Release(descriptor.pFinalObject);
444+
}
445+
}
446+
}
447+
448+
string stackTrace;
449+
if (stackTraceBuilder != null)
450+
{
451+
stackTrace = stackTraceBuilder.TrimEnd().ToString();
452+
stackTraceBuilder.Clear();
453+
}
454+
else
455+
{
456+
stackTrace = string.Empty;
457+
}
458+
459+
return stackTrace;
460+
}
461+
367462
/// <summary>
368463
/// Executes a script text
369464
/// </summary>

src/MsieJavaScriptEngine/ActiveScript/ActiveScriptWrapper.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ internal sealed class ActiveScriptWrapper
5252
/// </summary>
5353
private IntPtr _pActiveScriptGarbageCollector;
5454

55+
/// <summary>
56+
/// Pointer to an instance of 32-bit debug stack frame sniffer
57+
/// </summary>
58+
private IntPtr _pDebugStackFrameSniffer32;
59+
60+
/// <summary>
61+
/// Pointer to an instance of 64-bit debug stack frame sniffer
62+
/// </summary>
63+
private IntPtr _pDebugStackFrameSniffer64;
64+
5565
/// <summary>
5666
/// Instance of Active Script engine
5767
/// </summary>
@@ -72,6 +82,16 @@ internal sealed class ActiveScriptWrapper
7282
/// </summary>
7383
private IActiveScriptGarbageCollector _activeScriptGarbageCollector;
7484

85+
/// <summary>
86+
/// Instance of 32-bit debug stack frame sniffer
87+
/// </summary>
88+
private IDebugStackFrameSnifferEx32 _debugStackFrameSniffer32;
89+
90+
/// <summary>
91+
/// Instance of 64-bit debug stack frame sniffer
92+
/// </summary>
93+
private IDebugStackFrameSnifferEx64 _debugStackFrameSniffer64;
94+
7595
/// <summary>
7696
/// Last COM exception
7797
/// </summary>
@@ -105,22 +125,28 @@ public ActiveScriptWrapper(string clsid, ScriptLanguageVersion languageVersion)
105125
{
106126
_pActiveScriptParse64 = ComHelpers.QueryInterface<IActiveScriptParse64>(_pActiveScript);
107127
_pActiveScriptDebug64 = ComHelpers.QueryInterface<IActiveScriptDebug64>(_pActiveScript);
128+
_pDebugStackFrameSniffer64 = ComHelpers.QueryInterfaceNoThrow<IDebugStackFrameSnifferEx64>(_pActiveScript);
108129
}
109130
else
110131
{
111132
_pActiveScriptParse32 = ComHelpers.QueryInterface<IActiveScriptParse32>(_pActiveScript);
112133
_pActiveScriptDebug32 = ComHelpers.QueryInterface<IActiveScriptDebug32>(_pActiveScript);
134+
_pDebugStackFrameSniffer32 = ComHelpers.QueryInterfaceNoThrow<IDebugStackFrameSnifferEx32>(_pActiveScript);
113135
}
114136
_pActiveScriptGarbageCollector = ComHelpers.QueryInterfaceNoThrow<IActiveScriptGarbageCollector>(_pActiveScript);
115137

116138
_activeScript = (IActiveScript)Marshal.GetObjectForIUnknown(_pActiveScript);
117139
if (_is64Bit)
118140
{
119141
_activeScriptParse64 = (IActiveScriptParse64)_activeScript;
142+
_debugStackFrameSniffer64 = _pDebugStackFrameSniffer64 != IntPtr.Zero ?
143+
_activeScript as IDebugStackFrameSnifferEx64 : null;
120144
}
121145
else
122146
{
123147
_activeScriptParse32 = (IActiveScriptParse32)_activeScript;
148+
_debugStackFrameSniffer32 = _pDebugStackFrameSniffer32 != IntPtr.Zero ?
149+
_activeScript as IDebugStackFrameSnifferEx32 : null;
124150
}
125151
_activeScriptGarbageCollector = _activeScript as IActiveScriptGarbageCollector;
126152

@@ -301,6 +327,31 @@ public void EnumCodeContextsOfPosition(UIntPtr sourceContext, uint offset, uint
301327
ComHelpers.HResult.Check(result);
302328
}
303329

330+
public void EnumStackFrames(out IEnumDebugStackFrames enumFrames)
331+
{
332+
enumFrames = null;
333+
334+
if (_is64Bit)
335+
{
336+
if (_debugStackFrameSniffer64 != null)
337+
{
338+
_debugStackFrameSniffer64.EnumStackFrames(out enumFrames);
339+
}
340+
}
341+
else
342+
{
343+
if (_debugStackFrameSniffer32 != null)
344+
{
345+
_debugStackFrameSniffer32.EnumStackFrames(out enumFrames);
346+
}
347+
}
348+
349+
if (enumFrames == null)
350+
{
351+
enumFrames = new NullEnumDebugStackFrames();
352+
}
353+
}
354+
304355
/// <summary>
305356
/// The Active Script host calls this method to start garbage collection
306357
/// </summary>
@@ -325,21 +376,25 @@ public void Dispose()
325376
_activeScriptGarbageCollector = null;
326377
if (_is64Bit)
327378
{
379+
_debugStackFrameSniffer64 = null;
328380
_activeScriptParse64 = null;
329381
}
330382
else
331383
{
384+
_debugStackFrameSniffer32 = null;
332385
_activeScriptParse32 = null;
333386
}
334387

335388
ComHelpers.ReleaseAndEmpty(ref _pActiveScriptGarbageCollector);
336389
if (_is64Bit)
337390
{
391+
ComHelpers.ReleaseAndEmpty(ref _pDebugStackFrameSniffer64);
338392
ComHelpers.ReleaseAndEmpty(ref _pActiveScriptDebug64);
339393
ComHelpers.ReleaseAndEmpty(ref _pActiveScriptParse64);
340394
}
341395
else
342396
{
397+
ComHelpers.ReleaseAndEmpty(ref _pDebugStackFrameSniffer32);
343398
ComHelpers.ReleaseAndEmpty(ref _pActiveScriptDebug32);
344399
ComHelpers.ReleaseAndEmpty(ref _pActiveScriptParse32);
345400
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#if !NETSTANDARD1_3
2+
using System.Runtime.InteropServices;
3+
4+
namespace MsieJavaScriptEngine.ActiveScript.Debugging
5+
{
6+
/// <summary>
7+
/// Provides a way to enumerate the logical stack frames known by a component. Script engines typically
8+
/// implement this interface. The process debug manager uses this interface to find all stack frames
9+
/// associated with a given thread.
10+
/// </summary>
11+
[ComImport]
12+
[Guid("51973c19-cb0c-11d0-b5c9-00a0244a0e7a")]
13+
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
14+
internal interface IDebugStackFrameSnifferEx32 // : IDebugStackFrameSniffer
15+
{
16+
#region IDebugStackFrameSniffer methods
17+
18+
/// <summary>
19+
/// Returns an enumerator of stack frames for the current thread
20+
/// </summary>
21+
/// <param name="enumFrames">Enumerator of stack frames for the current thread</param>
22+
void EnumStackFrames(
23+
[Out] [MarshalAs(UnmanagedType.Interface)] out IEnumDebugStackFrames enumFrames
24+
);
25+
26+
#endregion
27+
28+
void EnumStackFramesEx32(
29+
[In] uint minimum,
30+
[Out] [MarshalAs(UnmanagedType.Interface)] out IEnumDebugStackFrames enumFrames
31+
);
32+
}
33+
}
34+
#endif

0 commit comments

Comments
 (0)