@@ -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 ) )
0 commit comments