From bc4038894edad0ecb6c62777a1b724953feb4140 Mon Sep 17 00:00:00 2001 From: Ian Craig Date: Mon, 12 Oct 2020 21:53:15 -0700 Subject: [PATCH 1/2] Enable deminifying call stack without parsing JS to support ES2015 --- .../IStackFrameDeminifier.cs | 2 +- ...r.cs => MethodNameStackFrameDeminifier.cs} | 6 +- ...ourcemapToolkit.CallstackDeminifier.csproj | 2 +- .../StackFrameDeminificationResult.cs | 5 + .../StackFrameDeminifier.cs | 34 ++++- .../StackTraceDeminfierFactory.cs | 38 ++++- .../StackTraceDeminifier.cs | 9 +- ...olkit.CallstackDeminifier.UnitTests.csproj | 1 + .../StackFrameDeminifierUnitTests.cs | 18 +-- ...tackTraceDeminifierMapOnlyEndToEndTests.cs | 144 ++++++++++++++++++ .../StackTraceDeminifierUnitTests.cs | 4 +- 11 files changed, 239 insertions(+), 24 deletions(-) rename src/SourceMapToolkit.CallstackDeminifier/{SimpleStackFrameDeminifier.cs => MethodNameStackFrameDeminifier.cs} (88%) create mode 100644 tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierMapOnlyEndToEndTests.cs diff --git a/src/SourceMapToolkit.CallstackDeminifier/IStackFrameDeminifier.cs b/src/SourceMapToolkit.CallstackDeminifier/IStackFrameDeminifier.cs index 97a48eb..bd9ab1b 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/IStackFrameDeminifier.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/IStackFrameDeminifier.cs @@ -6,6 +6,6 @@ internal interface IStackFrameDeminifier /// This method will deminify a single stack from from a minified stack trace. /// /// Returns a StackFrameDeminificationResult that contains a stack trace that has been translated to the original source code. The DeminificationError Property indicates if the StackFrame could not be deminified. DeminifiedStackFrame will not be null, but any properties of DeminifiedStackFrame could be null if the value could not be extracted. - StackFrameDeminificationResult DeminifyStackFrame(StackFrame stackFrame); + StackFrameDeminificationResult DeminifyStackFrame(StackFrame stackFrame, string callerSymbolName); } } \ No newline at end of file diff --git a/src/SourceMapToolkit.CallstackDeminifier/SimpleStackFrameDeminifier.cs b/src/SourceMapToolkit.CallstackDeminifier/MethodNameStackFrameDeminifier.cs similarity index 88% rename from src/SourceMapToolkit.CallstackDeminifier/SimpleStackFrameDeminifier.cs rename to src/SourceMapToolkit.CallstackDeminifier/MethodNameStackFrameDeminifier.cs index 79a5b27..4ce2059 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/SimpleStackFrameDeminifier.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/MethodNameStackFrameDeminifier.cs @@ -6,12 +6,12 @@ namespace SourcemapToolkit.CallstackDeminifier /// /// This class only deminfies the method name in a stack frame. It does not depend on having a source map available during runtime. /// - internal class SimpleStackFrameDeminifier : IStackFrameDeminifier + internal class MethodNameStackFrameDeminifier : IStackFrameDeminifier { protected readonly IFunctionMapConsumer _functionMapConsumer; protected readonly IFunctionMapStore _functionMapStore; - public SimpleStackFrameDeminifier(IFunctionMapStore functionMapStore, IFunctionMapConsumer functionMapConsumer) + public MethodNameStackFrameDeminifier(IFunctionMapStore functionMapStore, IFunctionMapConsumer functionMapConsumer) { _functionMapStore = functionMapStore; _functionMapConsumer = functionMapConsumer; @@ -19,7 +19,7 @@ public SimpleStackFrameDeminifier(IFunctionMapStore functionMapStore, IFunctionM /// /// This method will deminify the method name of a single stack from from a minified stack trace. /// - public virtual StackFrameDeminificationResult DeminifyStackFrame(StackFrame stackFrame) + public virtual StackFrameDeminificationResult DeminifyStackFrame(StackFrame stackFrame, string callerSymbolName) { StackFrameDeminificationResult result = new StackFrameDeminificationResult { diff --git a/src/SourceMapToolkit.CallstackDeminifier/SourcemapToolkit.CallstackDeminifier.csproj b/src/SourceMapToolkit.CallstackDeminifier/SourcemapToolkit.CallstackDeminifier.csproj index 1ae1ab8..57532e3 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/SourcemapToolkit.CallstackDeminifier.csproj +++ b/src/SourceMapToolkit.CallstackDeminifier/SourcemapToolkit.CallstackDeminifier.csproj @@ -66,7 +66,7 @@ - + diff --git a/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminificationResult.cs b/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminificationResult.cs index 8702de4..8f567d6 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminificationResult.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminificationResult.cs @@ -52,6 +52,11 @@ public class StackFrameDeminificationResult /// public StackFrame DeminifiedStackFrame { get; set; } + /// + /// The original name of the symbol at this frame's position + /// + public string DeminifiedSymbolName { get; set; } + /// /// An enum indicating if any errors occured when deminifying the stack frame. /// diff --git a/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminifier.cs b/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminifier.cs index 768275b..bf77c10 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminifier.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminifier.cs @@ -9,20 +9,26 @@ namespace SourcemapToolkit.CallstackDeminifier /// Since source maps take up a large amount of memory, this class consumes considerably /// more memory than SimpleStackFrame Deminifier during runtime. /// - internal class StackFrameDeminifier : SimpleStackFrameDeminifier + internal class StackFrameDeminifier : IStackFrameDeminifier { private readonly ISourceMapStore _sourceMapStore; + private readonly MethodNameStackFrameDeminifier _methodNameDeminifier = null; - public StackFrameDeminifier(ISourceMapStore sourceMapStore, IFunctionMapStore functionMapStore, IFunctionMapConsumer functionMapConsumer) : base (functionMapStore, functionMapConsumer) + public StackFrameDeminifier(ISourceMapStore sourceMapStore) { _sourceMapStore = sourceMapStore; } + public StackFrameDeminifier(ISourceMapStore sourceMapStore, IFunctionMapStore functionMapStore, IFunctionMapConsumer functionMapConsumer) : this(sourceMapStore) + { + _methodNameDeminifier = new MethodNameStackFrameDeminifier(functionMapStore, functionMapConsumer); + } + /// /// This method will deminify a single stack from from a minified stack trace. /// /// Returns a StackFrameDeminificationResult that contains a stack trace that has been translated to the original source code. The DeminificationError Property indicates if the StackFrame could not be deminified. DeminifiedStackFrame will not be null, but any properties of DeminifiedStackFrame could be null if the value could not be extracted. - public override StackFrameDeminificationResult DeminifyStackFrame(StackFrame stackFrame) + public StackFrameDeminificationResult DeminifyStackFrame(StackFrame stackFrame, string callerSymbolName) { if (stackFrame == null) { @@ -32,7 +38,26 @@ public override StackFrameDeminificationResult DeminifyStackFrame(StackFrame sta SourceMap sourceMap = _sourceMapStore.GetSourceMapForUrl(stackFrame.FilePath); SourcePosition generatedSourcePosition = stackFrame.SourcePosition; - StackFrameDeminificationResult result = base.DeminifyStackFrame(stackFrame); + StackFrameDeminificationResult result = null; + if (_methodNameDeminifier != null) + { + try + { + result = _methodNameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName); + } + catch { /* Continue to position deminification anyway */ } + } + + if (result == null) + { + result = new StackFrameDeminificationResult + { + DeminificationError = DeminificationError.None, + DeminifiedStackFrame = new StackFrame { MethodName = callerSymbolName ?? stackFrame.MethodName ?? "?" } + }; + } + + //_simpleDeminifier.DeminifyStackFrame(stackFrame); if (result.DeminificationError == DeminificationError.None) { MappingEntry generatedSourcePositionMappingEntry = @@ -56,6 +81,7 @@ public override StackFrameDeminificationResult DeminifyStackFrame(StackFrame sta result.DeminifiedStackFrame.FilePath = generatedSourcePositionMappingEntry?.OriginalFileName; result.DeminifiedStackFrame.SourcePosition = generatedSourcePositionMappingEntry?.OriginalSourcePosition; + result.DeminifiedSymbolName = generatedSourcePositionMappingEntry?.OriginalName; } return result; diff --git a/src/SourceMapToolkit.CallstackDeminifier/StackTraceDeminfierFactory.cs b/src/SourceMapToolkit.CallstackDeminifier/StackTraceDeminfierFactory.cs index 9cd65d8..ab22281 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/StackTraceDeminfierFactory.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/StackTraceDeminfierFactory.cs @@ -49,7 +49,41 @@ public static StackTraceDeminifier GetStackTraceDeminfier(ISourceMapProvider sou return new StackTraceDeminifier(stackFrameDeminifier, stackTraceParser); } - + + /// + /// Creates a StackTraceDeminifier which does not depend on JS files, and is ES2015+ compatible. + /// StackTrace deminifiers created with this method will keep source maps cached, and thus use significantly more memory during runtime than the ones generated with GetMethodNameOnlyStackTraceDeminfier. + /// + /// Consumers of the API should implement this interface, which provides the source map for a given JavaScript file. Throws ArgumentNullException if the parameter is set to null. + public static StackTraceDeminifier GetMapOnlyStackTraceDeminfier(ISourceMapProvider sourceMapProvider) + { + return GetMapOnlyStackTraceDeminfier(sourceMapProvider, new StackTraceParser()); + } + + /// + /// Creates a StackTraceDeminifier which does not depend on JS files, and is ES2015+ compatible. + /// StackTrace deminifiers created with this method will keep source maps cached, and thus use significantly more memory during runtime than the ones generated with GetMethodNameOnlyStackTraceDeminfier. + /// + /// Consumers of the API should implement this interface, which provides the source map for a given JavaScript file. Throws ArgumentNullException if the parameter is set to null. + /// Consumers of the API should implement this interface, which provides a parser for the stacktrace. Throws ArgumentNullException if the parameter is set to null. + public static StackTraceDeminifier GetMapOnlyStackTraceDeminfier(ISourceMapProvider sourceMapProvider, IStackTraceParser stackTraceParser) + { + if (sourceMapProvider == null) + { + throw new ArgumentNullException(nameof(sourceMapProvider)); + } + + if (stackTraceParser == null) + { + throw new ArgumentNullException(nameof(stackTraceParser)); + } + + ISourceMapStore sourceMapStore = new SourceMapStore(sourceMapProvider); + IStackFrameDeminifier stackFrameDeminifier = new StackFrameDeminifier(sourceMapStore); + + return new StackTraceDeminifier(stackFrameDeminifier, stackTraceParser); + } + /// /// Creates a StackTraceDeminifier that only deminifies the method names. StackTrace deminifiers created with this method will use significantly less memory during runtime than the /// @@ -71,7 +105,7 @@ public static StackTraceDeminifier GetMethodNameOnlyStackTraceDeminfier(ISourceM ValidateArguments(sourceMapProvider, generatedCodeProvider, stackTraceParser); SourceMapParser sourceMapParser = new SourceMapParser(); - IStackFrameDeminifier stackFrameDeminifier = new SimpleStackFrameDeminifier(new FunctionMapStore(generatedCodeProvider, (url) => sourceMapParser.ParseSourceMap(sourceMapProvider.GetSourceMapContentsForCallstackUrl(url))), new FunctionMapConsumer()); + IStackFrameDeminifier stackFrameDeminifier = new MethodNameStackFrameDeminifier(new FunctionMapStore(generatedCodeProvider, (url) => sourceMapParser.ParseSourceMap(sourceMapProvider.GetSourceMapContentsForCallstackUrl(url))), new FunctionMapConsumer()); return new StackTraceDeminifier(stackFrameDeminifier, stackTraceParser); } diff --git a/src/SourceMapToolkit.CallstackDeminifier/StackTraceDeminifier.cs b/src/SourceMapToolkit.CallstackDeminifier/StackTraceDeminifier.cs index 32f2116..f07d57f 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/StackTraceDeminifier.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/StackTraceDeminifier.cs @@ -28,9 +28,14 @@ public DeminifyStackTraceResult DeminifyStackTrace(string stackTraceString) result.Message = message; result.DeminifiedStackFrameResults = new List(); - foreach (StackFrame minifiedStackFrame in result.MinifiedStackFrames) + // Deminify frames in reverse order so we can pass the symbol name from caller + // (i.e. the function name) into the next level's deminification. + string callerSymbolName = null; + for (int i = result.MinifiedStackFrames.Count - 1; i >= 0; i--) { - result.DeminifiedStackFrameResults.Add(_stackFrameDeminifier.DeminifyStackFrame(minifiedStackFrame)); + var frame = _stackFrameDeminifier.DeminifyStackFrame(result.MinifiedStackFrames[i], callerSymbolName); + callerSymbolName = frame?.DeminifiedSymbolName; + result.DeminifiedStackFrameResults.Insert(0, frame); } return result; diff --git a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/SourcemapToolkit.CallstackDeminifier.UnitTests.csproj b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/SourcemapToolkit.CallstackDeminifier.UnitTests.csproj index 89df485..28608ae 100644 --- a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/SourcemapToolkit.CallstackDeminifier.UnitTests.csproj +++ b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/SourcemapToolkit.CallstackDeminifier.UnitTests.csproj @@ -97,6 +97,7 @@ + diff --git a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackFrameDeminifierUnitTests.cs b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackFrameDeminifierUnitTests.cs index 90adc73..d11ceb3 100644 --- a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackFrameDeminifierUnitTests.cs +++ b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackFrameDeminifierUnitTests.cs @@ -29,7 +29,7 @@ private IStackFrameDeminifier GetStackFrameDeminifierWithMockDependencies(ISourc if (useSimpleStackFrameDeminier) { - return new SimpleStackFrameDeminifier(functionMapStore, functionMapConsumer); + return new MethodNameStackFrameDeminifier(functionMapStore, functionMapConsumer); } else { @@ -45,7 +45,7 @@ public void DeminifyStackFrame_NullInputStackFrame_ThrowsException() StackFrame stackFrame = null; // Act - Assert.Throws( ()=> stackFrameDeminifier.DeminifyStackFrame(stackFrame)); + Assert.Throws( ()=> stackFrameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName: null)); } [Fact] @@ -56,7 +56,7 @@ public void DeminifyStackFrame_StackFrameNullProperties_DoesNotThrowException() IStackFrameDeminifier stackFrameDeminifier = GetStackFrameDeminifierWithMockDependencies(); // Act - StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame); + StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName: null); // Assert Assert.Null(stackFrameDeminification.DeminifiedStackFrame.MethodName); @@ -77,7 +77,7 @@ public void SimpleStackFrameDeminierDeminifyStackFrame_FunctionMapReturnsNull_No IStackFrameDeminifier stackFrameDeminifier = GetStackFrameDeminifierWithMockDependencies(functionMapStore: functionMapStore, useSimpleStackFrameDeminier:true); // Act - StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame); + StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName: null); // Assert Assert.Equal(DeminificationError.NoSourceCodeProvided, stackFrameDeminification.DeminificationError); @@ -102,7 +102,7 @@ public void SimpleStackFrameDeminierDeminifyStackFrame_GetWRappingFunctionForSou IStackFrameDeminifier stackFrameDeminifier = GetStackFrameDeminifierWithMockDependencies(functionMapStore: functionMapStore, functionMapConsumer: functionMapConsumer, useSimpleStackFrameDeminier: true); // Act - StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame); + StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName: null); // Assert Assert.Equal(DeminificationError.NoWrapingFunctionFound, stackFrameDeminification.DeminificationError); @@ -128,7 +128,7 @@ public void SimpleStackFrameDeminierDeminifyStackFrame_WrapingFunctionFound_NoDe IStackFrameDeminifier stackFrameDeminifier = GetStackFrameDeminifierWithMockDependencies(functionMapStore: functionMapStore, functionMapConsumer: functionMapConsumer, useSimpleStackFrameDeminier: true); // Act - StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame); + StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName: null); // Assert Assert.Equal(DeminificationError.None, stackFrameDeminification.DeminificationError); @@ -155,7 +155,7 @@ public void StackFrameDeminierDeminifyStackFrame_SourceMapProviderReturnsNull_No IStackFrameDeminifier stackFrameDeminifier = GetStackFrameDeminifierWithMockDependencies(functionMapStore: functionMapStore, functionMapConsumer: functionMapConsumer); // Act - StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame); + StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName: null); // Assert Assert.Equal(DeminificationError.NoSourceMap, stackFrameDeminification.DeminificationError); @@ -183,7 +183,7 @@ public void StackFrameDeminierDeminifyStackFrame_SourceMapParsingNull_SourceMapF IStackFrameDeminifier stackFrameDeminifier = GetStackFrameDeminifierWithMockDependencies(sourceMapStore: sourceMapStore,functionMapStore: functionMapStore, functionMapConsumer: functionMapConsumer); // Act - StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame); + StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName: null); // Assert Assert.Equal(DeminificationError.SourceMapFailedToParse, stackFrameDeminification.DeminificationError); @@ -213,7 +213,7 @@ public void StackFrameDeminierDeminifyStackFrame_SourceMapGeneratedMappingEntryN IStackFrameDeminifier stackFrameDeminifier = GetStackFrameDeminifierWithMockDependencies(sourceMapStore: sourceMapStore, functionMapStore: functionMapStore, functionMapConsumer: functionMapConsumer); // Act - StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame); + StackFrameDeminificationResult stackFrameDeminification = stackFrameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName: null); // Assert Assert.Equal(DeminificationError.NoMatchingMapingInSourceMap, stackFrameDeminification.DeminificationError); diff --git a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierMapOnlyEndToEndTests.cs b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierMapOnlyEndToEndTests.cs new file mode 100644 index 0000000..5ea6b79 --- /dev/null +++ b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierMapOnlyEndToEndTests.cs @@ -0,0 +1,144 @@ +using Xunit; +using Rhino.Mocks; +using SourcemapToolkit.SourcemapParser.UnitTests; + +namespace SourcemapToolkit.CallstackDeminifier.UnitTests +{ + + public class StackTraceDeminifierMapOnlyEndToEndTests + { + private const string GeneratedCodeString = "function causeCrash(){function n(){var n=16;n+=2;t(n)}function t(n){n=n+2;i(n)}function i(n){(function(){var t;console.log(t.length+n)})()}window.onerror=function(n,t,i,r,u){u?document.getElementById(\"callstackdisplay\").innerText=u.stack:window.event.error&&(document.getElementById(\"callstackdisplay\").innerText=window.event.error.stack)};n()}window.onload=function(){document.getElementById(\"crashbutton\").addEventListener(\"click\",function(){causeCrash()})};"; + private const string SourceMapString = "{\r\n\"version\":3,\r\n\"file\":\"crashcauser.min.js\",\r\n\"lineCount\":1,\r\n\"mappings\":\"AAAAA,SAASA,UAAU,CAAA,CACnB,CACIC,SAASA,CAAM,CAAA,CAAG,CACd,IAAIC,EAAwB,EAAE,CAC9BA,CAAsB,EAAG,CAAC,CAC1BC,CAAM,CAACD,CAAD,CAHQ,CAMlBC,SAASA,CAAM,CAACC,CAAD,CAAQ,CACnBA,CAAM,CAAEA,CAAM,CAAE,CAAC,CACjBC,CAAM,CAACD,CAAD,CAFa,CAKvBC,SAASA,CAAM,CAACD,CAAD,CAAQ,EAClB,QAAQ,CAAA,CAAG,CACR,IAAIE,CAAC,CACLC,OAAOC,IAAI,CAACF,CAACG,OAAQ,CAAEL,CAAZ,CAFH,EAGX,CAAA,CAJkB,CAOvBM,MAAMC,QAAS,CAAEC,QAAS,CAACC,CAAO,CAAEC,CAAM,CAAEC,CAAM,CAAEC,CAAK,CAAEC,CAAjC,CAAwC,CAC1DA,CAAJ,CACIC,QAAQC,eAAe,CAAC,kBAAD,CAAoBC,UAAW,CAAEH,CAAKI,MADjE,CAESX,MAAMY,MAAML,M,GACjBC,QAAQC,eAAe,CAAC,kBAAD,CAAoBC,UAAW,CAAEV,MAAMY,MAAML,MAAMI,OAJhB,C,CAOlEpB,CAAM,CAAA,CA1BV,CA6BAS,MAAMa,OAAQ,CAAEC,QAAS,CAAA,CAAQ,CAC7BN,QAAQC,eAAe,CAAC,aAAD,CAAeM,iBAAiB,CAAC,OAAO,CAAE,QAAS,CAAA,CAAG,CACzEzB,UAAU,CAAA,CAD+D,CAAtB,CAD1B,C\",\r\n\"sources\":[\"crashcauser.js\"],\r\n\"names\":[\"causeCrash\",\"level1\",\"longLocalVariableName\",\"level2\",\"input\",\"level3\",\"x\",\"console\",\"log\",\"length\",\"window\",\"onerror\",\"window.onerror\",\"message\",\"source\",\"lineno\",\"colno\",\"error\",\"document\",\"getElementById\",\"innerText\",\"stack\",\"event\",\"onload\",\"window.onload\",\"addEventListener\"]\r\n}"; + + + private StackTraceDeminifier GetStackTraceDeminifierWithDependencies() + { + ISourceMapProvider sourceMapProvider = MockRepository.GenerateStrictMock(); + sourceMapProvider.Stub(x => x.GetSourceMapContentsForCallstackUrl("http://localhost:11323/crashcauser.min.js")).Return(UnitTestUtils.StreamReaderFromString(SourceMapString)); + + ISourceCodeProvider sourceCodeProvider = MockRepository.GenerateStrictMock(); + sourceCodeProvider.Stub(x => x.GetSourceCode("http://localhost:11323/crashcauser.min.js")).Return(UnitTestUtils.StreamReaderFromString(GeneratedCodeString)); + + return StackTraceDeminfierFactory.GetMapOnlyStackTraceDeminfier(sourceMapProvider); + } + + private static void ValidateDeminifyStackTraceResults(DeminifyStackTraceResult results) + { + Assert.Equal(6, results.DeminifiedStackFrameResults.Count); + Assert.Equal(DeminificationError.None, results.DeminifiedStackFrameResults[0].DeminificationError); + Assert.Equal(16, results.DeminifiedStackFrameResults[0].DeminifiedStackFrame.SourcePosition.ZeroBasedLineNumber); + Assert.Equal("level3", results.DeminifiedStackFrameResults[1].DeminifiedStackFrame.MethodName); + Assert.Equal("level2", results.DeminifiedStackFrameResults[2].DeminifiedStackFrame.MethodName); + Assert.Equal("level1", results.DeminifiedStackFrameResults[3].DeminifiedStackFrame.MethodName); + Assert.Equal("causeCrash", results.DeminifiedStackFrameResults[4].DeminifiedStackFrame.MethodName); + Assert.Equal(32, results.DeminifiedStackFrameResults[5].DeminifiedStackFrame.SourcePosition.ZeroBasedLineNumber); + } + + [Fact] + public void DeminifyStackTrace_ChromeStackTraceString_CorrectDeminificationWhenPossible() + { + // Arrange + StackTraceDeminifier stackTraceDeminifier = GetStackTraceDeminifierWithDependencies(); + string chromeStackTrace = @"TypeError: Cannot read property 'length' of undefined + at http://localhost:11323/crashcauser.min.js:1:125 + at i (http://localhost:11323/crashcauser.min.js:1:137) + at t (http://localhost:11323/crashcauser.min.js:1:75) + at n (http://localhost:11323/crashcauser.min.js:1:50) + at causeCrash (http://localhost:11323/crashcauser.min.js:1:341) + at HTMLButtonElement. (http://localhost:11323/crashcauser.min.js:1:445)"; + + // Act + DeminifyStackTraceResult results = stackTraceDeminifier.DeminifyStackTrace(chromeStackTrace); + + // Assert + ValidateDeminifyStackTraceResults(results); + } + + [Fact] + public void DeminifyStackTrace_FireFoxStackTraceString_CorrectDeminificationWhenPossible() + { + // Arrange + StackTraceDeminifier stackTraceDeminifier = GetStackTraceDeminifierWithDependencies(); + string fireFoxStackTrace = @"i/<@http://localhost:11323/crashcauser.min.js:1:112 +i@http://localhost:11323/crashcauser.min.js:1:95 +t@http://localhost:11323/crashcauser.min.js:1:75 +n@http://localhost:11323/crashcauser.min.js:1:50 +causeCrash@http://localhost:11323/crashcauser.min.js:1:341 +window.onload/<@http://localhost:11323/crashcauser.min.js:1:445"; + + // Act + DeminifyStackTraceResult results = stackTraceDeminifier.DeminifyStackTrace(fireFoxStackTrace); + + // Assert + ValidateDeminifyStackTraceResults(results); + } + + [Fact] + public void DeminifyStackTrace_IE11StackTraceString_CorrectDeminificationWhenPossible() + { + // Arrange + StackTraceDeminifier stackTraceDeminifier = GetStackTraceDeminifierWithDependencies(); + string ieStackTrace = @"TypeError: Unable to get property 'length' of undefined or null reference + at Anonymous function (http://localhost:11323/crashcauser.min.js:1:112) + at i (http://localhost:11323/crashcauser.min.js:1:95) + at t (http://localhost:11323/crashcauser.min.js:1:75) + at n (http://localhost:11323/crashcauser.min.js:1:50) + at causeCrash (http://localhost:11323/crashcauser.min.js:1:341) + at Anonymous function (http://localhost:11323/crashcauser.min.js:1:445)"; + + // Act + DeminifyStackTraceResult results = stackTraceDeminifier.DeminifyStackTrace(ieStackTrace); + + // Assert + ValidateDeminifyStackTraceResults(results); + } + + [Fact] + public void DeminifyStackTrace_EdgeStackTraceString_CorrectDeminificationWhenPossible() + { + // Arrange + StackTraceDeminifier stackTraceDeminifier = GetStackTraceDeminifierWithDependencies(); + string dgeStackTrace = @"TypeError: Unable to get property 'length' of undefined or null reference + at Anonymous function (http://localhost:11323/crashcauser.min.js:1:112) + at i (http://localhost:11323/crashcauser.min.js:1:95) + at t (http://localhost:11323/crashcauser.min.js:1:75) + at n (http://localhost:11323/crashcauser.min.js:1:50) + at causeCrash (http://localhost:11323/crashcauser.min.js:1:341) + at Anonymous function (http://localhost:11323/crashcauser.min.js:1:445)"; + + // Act + DeminifyStackTraceResult results = stackTraceDeminifier.DeminifyStackTrace(dgeStackTrace); + + // Assert + ValidateDeminifyStackTraceResults(results); + } + + [Fact] + public void DeminifyResultToString_SuccessfullyDeminified_AllLinesDeminified() + { + // Arrange + StackTraceDeminifier stackTraceDeminifier = GetStackTraceDeminifierWithDependencies(); + string ieStackTrace = @"TypeError: Unable to get property 'length' of undefined or null reference + at Anonymous function (http://localhost:11323/crashcauser.min.js:1:112) + at i (http://localhost:11323/crashcauser.min.js:1:95) + at t (http://localhost:11323/crashcauser.min.js:1:75) + at n (http://localhost:11323/crashcauser.min.js:1:50) + at causeCrash (http://localhost:11323/crashcauser.min.js:1:341) + at http://localhost:11323/crashcauser.min.js:1:445"; + DeminifyStackTraceResult results = stackTraceDeminifier.DeminifyStackTrace(ieStackTrace); + string exectedResult = @"TypeError: Unable to get property 'length' of undefined or null reference + at Anonymous function in crashcauser.js:17:13 + at level3 in crashcauser.js:15:10 + at level2 in crashcauser.js:11:9 + at level1 in crashcauser.js:6:9 + at causeCrash in crashcauser.js:28:5 + at ? in crashcauser.js:33:9"; + + // Act + string formatted = results.ToString(); + + // Assert + Assert.Equal(exectedResult.Replace("\r", ""), formatted.Replace("\r", "")); + } + } +} \ No newline at end of file diff --git a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierUnitTests.cs b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierUnitTests.cs index f670642..b64885c 100644 --- a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierUnitTests.cs +++ b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierUnitTests.cs @@ -36,7 +36,7 @@ public void DeminifyStackTrace_UnableToDeminifyStackTrace_ResultContainsNullDemi stackTraceParser.Stub(x => x.ParseStackTrace(stackTraceString, out string message)).Return(minifiedStackFrames).OutRef("Error example"); IStackFrameDeminifier stackFrameDeminifier = MockRepository.GenerateStrictMock(); - stackFrameDeminifier.Stub(x => x.DeminifyStackFrame(minifiedStackFrames[0])).Return(null); + stackFrameDeminifier.Stub(x => x.DeminifyStackFrame(minifiedStackFrames[0], null)).Return(null); StackTraceDeminifier stackTraceDeminifier = new StackTraceDeminifier(stackFrameDeminifier, stackTraceParser); @@ -60,7 +60,7 @@ public void DeminifyStackTrace_AbleToDeminifyStackTrace_ResultContainsDeminified IStackFrameDeminifier stackFrameDeminifier = MockRepository.GenerateStrictMock(); StackFrameDeminificationResult stackFrameDeminification = new StackFrameDeminificationResult(); - stackFrameDeminifier.Stub(x => x.DeminifyStackFrame(minifiedStackFrames[0])).Return(stackFrameDeminification); + stackFrameDeminifier.Stub(x => x.DeminifyStackFrame(minifiedStackFrames[0], null)).Return(stackFrameDeminification); StackTraceDeminifier stackTraceDeminifier = new StackTraceDeminifier(stackFrameDeminifier, stackTraceParser); From 049471d3567ff8bd681377bb8fb7f8488b40dece Mon Sep 17 00:00:00 2001 From: Ian Craig Date: Tue, 13 Oct 2020 10:29:40 -0700 Subject: [PATCH 2/2] Move exception handling into FunctionMapGenerator --- .../DeminifyStackTraceResult.cs | 9 +++++++- .../FunctionMapGenerator.cs | 16 +++++++++++--- .../StackFrameDeminifier.cs | 21 ++++++++----------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/SourceMapToolkit.CallstackDeminifier/DeminifyStackTraceResult.cs b/src/SourceMapToolkit.CallstackDeminifier/DeminifyStackTraceResult.cs index b882d30..887fd79 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/DeminifyStackTraceResult.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/DeminifyStackTraceResult.cs @@ -17,7 +17,14 @@ public override string ToString() for (int i = 0; i < DeminifiedStackFrameResults.Count; i++) { StackFrame deminFrame = DeminifiedStackFrameResults[i].DeminifiedStackFrame; - StackFrame frame = string.IsNullOrEmpty(deminFrame.MethodName) ? MinifiedStackFrames[i] : deminFrame; + + // Use deminified info wherever possible, merging if necessary so we always print a full frame + StackFrame frame = new StackFrame() + { + MethodName = deminFrame.MethodName ?? MinifiedStackFrames[i].MethodName, + SourcePosition = deminFrame.SourcePosition ?? MinifiedStackFrames[i].SourcePosition, + FilePath = deminFrame.SourcePosition != null ? deminFrame.FilePath : MinifiedStackFrames[i].FilePath + }; output += $"{Environment.NewLine} {frame}"; } diff --git a/src/SourceMapToolkit.CallstackDeminifier/FunctionMapGenerator.cs b/src/SourceMapToolkit.CallstackDeminifier/FunctionMapGenerator.cs index f232447..1cad683 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/FunctionMapGenerator.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/FunctionMapGenerator.cs @@ -20,11 +20,21 @@ public List GenerateFunctionMap(StreamReader sourceCodeStreamR return null; } - List result = ParseSourceCode(sourceCodeStreamReader); + List result; + try + { + result = ParseSourceCode(sourceCodeStreamReader); - foreach (FunctionMapEntry functionMapEntry in result) + foreach (FunctionMapEntry functionMapEntry in result) + { + functionMapEntry.DeminfifiedMethodName = GetDeminifiedMethodNameFromSourceMap(functionMapEntry, sourceMap); + } + } + catch { - functionMapEntry.DeminfifiedMethodName = GetDeminifiedMethodNameFromSourceMap(functionMapEntry, sourceMap); + // Failed to parse JavaScript source. This is common as the JS parser does not support ES2015+. + // Continue to regular source map deminification. + result = null; } return result; diff --git a/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminifier.cs b/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminifier.cs index bf77c10..1d3cf9b 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminifier.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/StackFrameDeminifier.cs @@ -41,23 +41,18 @@ public StackFrameDeminificationResult DeminifyStackFrame(StackFrame stackFrame, StackFrameDeminificationResult result = null; if (_methodNameDeminifier != null) { - try - { - result = _methodNameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName); - } - catch { /* Continue to position deminification anyway */ } + result = _methodNameDeminifier.DeminifyStackFrame(stackFrame, callerSymbolName); } - if (result == null) + if (result == null || result.DeminificationError == DeminificationError.NoSourceCodeProvided) { result = new StackFrameDeminificationResult { DeminificationError = DeminificationError.None, - DeminifiedStackFrame = new StackFrame { MethodName = callerSymbolName ?? stackFrame.MethodName ?? "?" } + DeminifiedStackFrame = new StackFrame { MethodName = callerSymbolName } }; } - //_simpleDeminifier.DeminifyStackFrame(stackFrame); if (result.DeminificationError == DeminificationError.None) { MappingEntry generatedSourcePositionMappingEntry = @@ -78,10 +73,12 @@ public StackFrameDeminificationResult DeminifyStackFrame(StackFrame stackFrame, result.DeminificationError = DeminificationError.NoMatchingMapingInSourceMap; } } - - result.DeminifiedStackFrame.FilePath = generatedSourcePositionMappingEntry?.OriginalFileName; - result.DeminifiedStackFrame.SourcePosition = generatedSourcePositionMappingEntry?.OriginalSourcePosition; - result.DeminifiedSymbolName = generatedSourcePositionMappingEntry?.OriginalName; + else + { + result.DeminifiedStackFrame.FilePath = generatedSourcePositionMappingEntry.OriginalFileName; + result.DeminifiedStackFrame.SourcePosition = generatedSourcePositionMappingEntry.OriginalSourcePosition; + result.DeminifiedSymbolName = generatedSourcePositionMappingEntry.OriginalName; + } } return result;