Skip to content

Commit 4702027

Browse files
authored
Fix inalid stack frame type (#172)
* Stack frame algorithm improvements * Parse correctly xbox stack frames without source information from the managed thread
1 parent d998c98 commit 4702027

File tree

2 files changed

+154
-80
lines changed

2 files changed

+154
-80
lines changed

Runtime/Model/BacktraceUnhandledException.cs

Lines changed: 127 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace Backtrace.Unity.Model
1212
public class BacktraceUnhandledException : Exception
1313
{
1414
private bool _header = false;
15+
16+
private static string[] _javaExtensions = new string[] { ".java", ".kt", "java." };
1517
public bool Header
1618
{
1719
get
@@ -50,12 +52,21 @@ public override string StackTrace
5052
public BacktraceUnhandledException(string message, string stacktrace) : base(message)
5153
{
5254
Type = LogType.Exception;
53-
StackFrames = new List<BacktraceStackFrame>();
5455
_message = message;
5556
_stacktrace = stacktrace;
5657
if (!string.IsNullOrEmpty(stacktrace))
5758
{
58-
ConvertStackFrames();
59+
IEnumerable<string> frames = _stacktrace.Split('\n');
60+
var stackFrameHeader = frames.ElementAt(0);
61+
var stackTraceMessage = GetStackTraceErrorMessage(stackFrameHeader);
62+
if (!string.IsNullOrEmpty(stackTraceMessage))
63+
{
64+
_message = stackTraceMessage;
65+
_header = true;
66+
frames = frames.Skip(1);
67+
}
68+
69+
StackFrames = ConvertStackFrames(frames);
5970
}
6071

6172
if (string.IsNullOrEmpty(stacktrace) || StackFrames.Count == 0)
@@ -70,16 +81,36 @@ public BacktraceUnhandledException(string message, string stacktrace) : base(mes
7081

7182
}
7283

84+
private string GetStackTraceErrorMessage(string beginningOfTheFrame)
85+
{
86+
beginningOfTheFrame = beginningOfTheFrame.Trim();
87+
// verify if the exception message has classifier
88+
var indexOfExceptionClassifier = beginningOfTheFrame.IndexOf("Exception:");
89+
if (indexOfExceptionClassifier != -1)
90+
{
91+
return beginningOfTheFrame;
92+
}
93+
// verify if the exception message looks like a stack frame based on the exception arguments
94+
if (beginningOfTheFrame.IndexOf('(') == -1 || beginningOfTheFrame.IndexOf(')') == -1)
95+
{
96+
return beginningOfTheFrame;
97+
}
98+
99+
return string.Empty;
100+
101+
}
102+
73103
/// <summary>
74104
/// Convert Unity error log message to Stack trace that Backtrace uses
75105
/// in an exception report. Method below support default Unity and Android stack trace.
76106
/// </summary>
77-
private void ConvertStackFrames()
107+
private List<BacktraceStackFrame> ConvertStackFrames(IEnumerable<string> frames)
78108
{
79-
var frames = _stacktrace.Split('\n');
80-
for (int frameIndex = 0; frameIndex < frames.Length; frameIndex++)
109+
var result = new List<BacktraceStackFrame>();
110+
111+
for (int frameIndex = 0; frameIndex < frames.Count(); frameIndex++)
81112
{
82-
var frame = frames[frameIndex];
113+
var frame = frames.ElementAt(frameIndex);
83114
if (string.IsNullOrEmpty(frame))
84115
{
85116
continue;
@@ -91,61 +122,76 @@ private void ConvertStackFrames()
91122
int methodNameEndIndex = frameString.IndexOf(')');
92123
if (methodNameEndIndex == -1)
93124
{
94-
// apply error message
95-
if (frameIndex == 0)
96-
{
97-
if (string.IsNullOrEmpty(_message))
98-
{
99-
_message = frameString;
100-
}
101-
_header = true;
102-
continue;
103-
}
104-
else
105-
{
106-
//invalid stack frame
107-
continue;
108-
}
125+
//invalid stack frame
126+
result.Add(new BacktraceStackFrame() { FunctionName = frame });
127+
continue;
128+
109129
}
110130

111131
//methodname index should be greater than 0 AND '(' should be before ')'
112132
if (methodNameEndIndex < 1 && frameString[methodNameEndIndex - 1] != '(')
113133
{
114-
//invalid stack frame
115-
return;
134+
result.Add(new BacktraceStackFrame()
135+
{
136+
FunctionName = frame
137+
});
116138
}
117139

118-
BacktraceStackFrame stackFrame = null;
119-
if (frameString.StartsWith("0x"))
120-
{
121-
stackFrame = SetNativeStackTraceInformation(frameString);
122-
}
123-
else if (frameString.StartsWith("#"))
124-
{
125-
stackFrame = SetJITStackTraceInformation(frameString);
126-
}
127-
else if (frameString.IndexOf('(', methodNameEndIndex + 1) > -1)
128-
{
129-
stackFrame = SetDefaultStackTraceInformation(frameString);
130-
}
131-
else
132-
{
133-
stackFrame = SetAndroidStackTraceInformation(frameString);
134-
}
140+
result.Add(ConvertFrame(frameString, methodNameEndIndex));
141+
}
142+
return result;
143+
}
135144

136-
// if function name is null - try to apply default parser
137-
if (stackFrame == null || string.IsNullOrEmpty(stackFrame.FunctionName))
145+
private BacktraceStackFrame ConvertFrame(string frameString, int methodNameEndIndex)
146+
{
147+
if (frameString.StartsWith("0x"))
148+
{
149+
return SetNativeStackTraceInformation(frameString);
150+
}
151+
else if (frameString.StartsWith("#"))
152+
{
153+
return SetJITStackTraceInformation(frameString);
154+
}
155+
// allow to execute this code in the editor
156+
// to validate parser via unit tests
157+
#if UNITY_ANDROID || UNITY_EDITOR
158+
// verify if the stack trace is from Untiy by checking if the
159+
// by checking source code location
160+
const char argumentStartInitialChar = '(';
161+
var sourceCodeStartIndex = frameString.IndexOf(argumentStartInitialChar, methodNameEndIndex + 1);
162+
if (sourceCodeStartIndex > -1)
163+
{
164+
return SetDefaultStackTraceInformation(frameString, methodNameEndIndex);
165+
}
166+
// verify if frame has parameters that contain source code information
167+
var methodStartIndex = frameString.IndexOf(argumentStartInitialChar);
168+
if (methodStartIndex == -1)
169+
{
170+
return new BacktraceStackFrame()
138171
{
139-
stackFrame = new BacktraceStackFrame()
140-
{
141-
FunctionName = frameString
142-
};
172+
FunctionName = frameString
173+
};
174+
}
175+
// add length of the '('
176+
methodStartIndex += 1;
177+
var methodArguments = frameString.Substring(methodStartIndex, methodNameEndIndex - methodStartIndex);
178+
if (methodArguments.IndexOf(':') != -1 || methodArguments == "Unknown Source")
179+
{
180+
return SetAndroidStackTraceInformation(frameString, methodStartIndex, methodNameEndIndex);
181+
}
182+
// check if popular extensions are available in the frame to determine if
183+
// the frame has any reference to java.
184+
for (int i = 0; i < _javaExtensions.Length; i++)
185+
{
186+
if (frameString.IndexOf(_javaExtensions[i]) != -1)
187+
{
188+
return SetAndroidStackTraceInformation(frameString, methodStartIndex, methodNameEndIndex);
143189
}
144-
145-
StackFrames.Add(stackFrame);
190+
}
191+
#endif
192+
return SetDefaultStackTraceInformation(frameString, methodNameEndIndex);
146193

147194

148-
}
149195
}
150196

151197
/// <summary>
@@ -155,9 +201,10 @@ private void ConvertStackFrames()
155201
/// <returns>Backtrace stack frame</returns>
156202
private BacktraceStackFrame SetJITStackTraceInformation(string frameString)
157203
{
158-
159-
var stackFrame = new BacktraceStackFrame();
160-
stackFrame.StackFrameType = Types.BacktraceStackFrameType.Native;
204+
var stackFrame = new BacktraceStackFrame
205+
{
206+
StackFrameType = Types.BacktraceStackFrameType.Native
207+
};
161208
if (!frameString.StartsWith("#"))
162209
{
163210
//handle sitaution when we detected jit stack trace
@@ -206,10 +253,17 @@ private BacktraceStackFrame SetJITStackTraceInformation(string frameString)
206253
/// <returns>Backtrace stack frame</returns>
207254
private BacktraceStackFrame SetNativeStackTraceInformation(string frameString)
208255
{
209-
var stackFrame = new BacktraceStackFrame();
210-
stackFrame.StackFrameType = Types.BacktraceStackFrameType.Native;
256+
var stackFrame = new BacktraceStackFrame
257+
{
258+
StackFrameType = Types.BacktraceStackFrameType.Native
259+
};
211260
// parse address
212261
var addressSubstringIndex = frameString.IndexOf(' ');
262+
if (addressSubstringIndex == -1)
263+
{
264+
stackFrame.FunctionName = frameString;
265+
return stackFrame;
266+
}
213267
stackFrame.Address = frameString.Substring(0, addressSubstringIndex);
214268
var indexPointer = addressSubstringIndex + 1;
215269

@@ -260,31 +314,27 @@ private BacktraceStackFrame SetNativeStackTraceInformation(string frameString)
260314
/// Try to convert Android stack frame string to Backtrace stack frame
261315
/// </summary>
262316
/// <param name="frameString">Android stack frame</param>
317+
/// <param name="parameterStart">Index of parameters start character '('</param>
318+
/// <param name="frameString">Index of paramters end character ')'</param>
263319
/// <returns>Backtrace stack frame</returns>
264-
private BacktraceStackFrame SetAndroidStackTraceInformation(string frameString)
320+
private BacktraceStackFrame SetAndroidStackTraceInformation(string frameString, int parameterStart, int parameterEnd)
265321
{
266-
// validate if stack trace is from Android
267-
// try parse method and line number available in the function parameter
268-
var parameterStart = frameString.LastIndexOf('(') + 1;
269-
var parameterEnd = frameString.LastIndexOf(')');
270-
271-
var stackFrame = new BacktraceStackFrame();
272-
stackFrame.StackFrameType = Types.BacktraceStackFrameType.Android;
273-
if (parameterStart != -1 && parameterEnd != -1 && parameterEnd - parameterStart > 1)
322+
var stackFrame = new BacktraceStackFrame
274323
{
275-
stackFrame.FunctionName = frameString.Substring(0, parameterStart - 1);
276-
var possibleSourceCodeInformation = frameString.Substring(parameterStart, parameterEnd - parameterStart);
324+
FunctionName = frameString.Substring(0, parameterStart - 1),
325+
StackFrameType = Types.BacktraceStackFrameType.Android
326+
};
327+
var possibleSourceCodeInformation = frameString.Substring(parameterStart, parameterEnd - parameterStart);
277328

278-
var sourceCodeInformation = possibleSourceCodeInformation.Split(':');
279-
if (sourceCodeInformation.Length == 2)
280-
{
281-
stackFrame.Library = sourceCodeInformation[0];
282-
int.TryParse(sourceCodeInformation[1], out stackFrame.Line);
283-
}
284-
else if (frameString.StartsWith("java.lang") || possibleSourceCodeInformation == "Unknown Source")
285-
{
286-
stackFrame.Library = possibleSourceCodeInformation;
287-
}
329+
var sourceCodeInformation = possibleSourceCodeInformation.Split(':');
330+
if (sourceCodeInformation.Length == 2)
331+
{
332+
stackFrame.Library = sourceCodeInformation[0];
333+
int.TryParse(sourceCodeInformation[1], out stackFrame.Line);
334+
}
335+
else if (frameString.StartsWith("java.lang") || possibleSourceCodeInformation == "Unknown Source")
336+
{
337+
stackFrame.Library = possibleSourceCodeInformation;
288338
}
289339

290340
return stackFrame;
@@ -296,16 +346,13 @@ private BacktraceStackFrame SetAndroidStackTraceInformation(string frameString)
296346
/// <param name="frameString">Unity stack frame</param>
297347
/// <param name="sourceInformationStartIndex"></param>
298348
/// <returns></returns>
299-
private BacktraceStackFrame SetDefaultStackTraceInformation(string frameString)
349+
private BacktraceStackFrame SetDefaultStackTraceInformation(string frameString, int methodNameEndIndex)
300350
{
301351
const string wrapperPrefix = "(wrapper remoting-invoke-with-check)";
302352
if (frameString.StartsWith(wrapperPrefix))
303353
{
304354
frameString = frameString.Replace(wrapperPrefix, string.Empty);
305355
}
306-
// find method parameters
307-
int methodNameEndIndex = frameString.IndexOf(')');
308-
309356
// detect source code information - format : 'at (...)'
310357

311358
// find source code start based on method parameter start index
@@ -353,7 +400,7 @@ private BacktraceStackFrame SetDefaultStackTraceInformation(string frameString)
353400
return result;
354401
}
355402
var substring = sourceString.Substring(atSeparator, endLine);
356-
403+
357404
result.Library = (substring == null ? string.Empty : substring.Trim());
358405

359406
if (!string.IsNullOrEmpty(result.Library))

Tests/Runtime/BacktraceStackTraceTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Backtrace.Unity.Model;
2+
using Backtrace.Unity.Types;
23
using NUnit.Framework;
34
using System;
45
using System.Collections;
@@ -540,6 +541,32 @@ public void JITStackTrace_ShouldParseCorrectlyJITStackTrace_StackTraceObjectIsGe
540541
Assert.AreEqual(backtraceUnhandledException.StackFrames[4].FunctionName, functioNamewithMonoJitCodePrefix);
541542
}
542543

544+
[Test]
545+
public void XboxStackTrace_ShouldIgnoreAndroidFrames_ShouldSetCsharpExtensionCorrectly()
546+
{
547+
548+
var functionName = "Can't delete buffers from graphics jobs";
549+
var xboxStackTrace = string.Format(@" {0}
550+
Unity.Entities.ComponentDependencyManager:CompleteReadAndWriteDependencyNoChecks(Int32)
551+
Unity.Entities.ComponentDependencyManager:CompleteDependenciesNoChecks(Int32*, Int32, Int32*, Int32)
552+
Unity.Entities.SystemBase:CompleteDependency()
553+
Unity.Entities.SystemBase:Update()
554+
Unity.Entities.ComponentSystemGroup:UpdateAllSystems()
555+
Unity.Entities.ComponentSystem:Update()",
556+
functionName);
557+
558+
559+
var backtraceUnhandledException = new BacktraceUnhandledException("test message", xboxStackTrace);
560+
561+
Assert.AreEqual(functionName, backtraceUnhandledException.Message);
562+
Assert.AreEqual(6, backtraceUnhandledException.StackFrames.Count);
563+
foreach (var frame in backtraceUnhandledException.StackFrames)
564+
{
565+
Assert.AreEqual(BacktraceStackFrameType.Dotnet, frame.StackFrameType);
566+
Assert.IsTrue(frame.FunctionName.StartsWith("Unity.Entities."));
567+
}
568+
}
569+
543570
internal string ConvertStackTraceToString(List<SampleStackFrame> data)
544571
{
545572
var stringBuilder = new StringBuilder();

0 commit comments

Comments
 (0)