1 '''
2 Symptom
3
4 Represents one symptom which may appear in a crash signature.
5
6 @author: Christian Holler (:decoder)
7
8 @license:
9
10 This Source Code Form is subject to the terms of the Mozilla Public
11 License, v. 2.0. If a copy of the MPL was not distributed with this
12 file, You can obtain one at http://mozilla.org/MPL/2.0/.
13
14 @contact: choller@mozilla.com
15 '''
16
17
18 from __future__ import print_function
19
20 from abc import ABCMeta, abstractmethod
21 import json
22 from FTB.Signatures import JSONHelper
23 from FTB.Signatures.Matchers import StringMatch, NumberMatch
26 '''
27 Abstract base class that provides a method to instantiate the right sub class.
28 It also supports generating a CrashSignature based on the stored information.
29 '''
30 __metaclass__ = ABCMeta
31
33
34 self.jsonsrc = json.dumps(jsonObj, indent=2)
35 self.jsonobj = jsonObj
36
39
40 @staticmethod
42 '''
43 Create the appropriate Symptom based on the given object (decoded from JSON)
44
45 @type obj: map
46 @param obj: Object as decoded from JSON
47
48 @rtype: Symptom
49 @return: Symptom subclass instance matching the given object
50 '''
51 if not "type" in obj:
52 raise RuntimeError("Missing symptom type in object")
53
54 stype = obj["type"]
55
56 if (stype == "output"):
57 return OutputSymptom(obj)
58 elif (stype == "stackFrame"):
59 return StackFrameSymptom(obj)
60 elif (stype == "stackSize"):
61 return StackSizeSymptom(obj)
62 elif (stype == "crashAddress"):
63 return CrashAddressSymptom(obj)
64 elif (stype == "instruction"):
65 return InstructionSymptom(obj)
66 elif (stype == "testcase"):
67 return TestcaseSymptom(obj)
68 elif (stype == "stackFrames"):
69 return StackFramesSymptom(obj)
70 else:
71 raise RuntimeError("Unknown symptom type: %s" % stype)
72
73 @abstractmethod
75 '''
76 Check if the symptom matches the given crash information
77
78 @type crashInfo: CrashInfo
79 @param crashInfo: The crash information to check against
80
81 @rtype: bool
82 @return: True if the symptom matches, False otherwise
83 '''
84 return
85
89 '''
90 Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly.
91 '''
92 Symptom.__init__(self, obj)
93 self.output = StringMatch(JSONHelper.getObjectOrStringChecked(obj, "value", True))
94 self.src = JSONHelper.getStringChecked(obj, "src")
95
96 if self.src != None:
97 self.src = self.src.lower()
98 if self.src != "stderr" and self.src != "stdout" and self.src != "crashdata":
99 raise RuntimeError("Invalid source specified: %s" % self.src)
100
102 '''
103 Check if the symptom matches the given crash information
104
105 @type crashInfo: CrashInfo
106 @param crashInfo: The crash information to check against
107
108 @rtype: bool
109 @return: True if the symptom matches, False otherwise
110 '''
111 checkedOutput = []
112
113 if self.src == None:
114 checkedOutput.extend(crashInfo.rawStdout)
115 checkedOutput.extend(crashInfo.rawStderr)
116 checkedOutput.extend(crashInfo.rawCrashData)
117 elif (self.src == "stdout"):
118 checkedOutput = crashInfo.rawStdout
119 elif (self.src == "stderr"):
120 checkedOutput = crashInfo.rawStderr
121 else:
122 checkedOutput = crashInfo.rawCrashData
123
124 for line in checkedOutput:
125 if self.output.matches(line):
126 return True
127
128 return False
129
144
146 '''
147 Check if the symptom matches the given crash information
148
149 @type crashInfo: CrashInfo
150 @param crashInfo: The crash information to check against
151
152 @rtype: bool
153 @return: True if the symptom matches, False otherwise
154 '''
155
156 for idx in range(len(crashInfo.backtrace)):
157
158 if self.frameNumber.matches(idx):
159 if self.functionName.matches(crashInfo.backtrace[idx]):
160 return True
161
162 return False
163
171
173 '''
174 Check if the symptom matches the given crash information
175
176 @type crashInfo: CrashInfo
177 @param crashInfo: The crash information to check against
178
179 @rtype: bool
180 @return: True if the symptom matches, False otherwise
181 '''
182 return self.stackSize.matches(len(crashInfo.backtrace))
183
191
193 '''
194 Check if the symptom matches the given crash information
195
196 @type crashInfo: CrashInfo
197 @param crashInfo: The crash information to check against
198
199 @rtype: bool
200 @return: True if the symptom matches, False otherwise
201 '''
202
203
204 return self.address.matches(crashInfo.crashAddress)
205
208 '''
209 Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly.
210 '''
211 Symptom.__init__(self, obj)
212 self.registerNames = JSONHelper.getArrayChecked(obj, "registerNames")
213 self.instructionName = JSONHelper.getObjectOrStringChecked(obj, "instructionName")
214
215 if self.instructionName != None:
216 self.instructionName = StringMatch(self.instructionName)
217 elif self.registerNames == None or len(self.registerNames) == 0:
218 raise RuntimeError("Must provide at least instruction name or register names")
219
221 '''
222 Check if the symptom matches the given crash information
223
224 @type crashInfo: CrashInfo
225 @param crashInfo: The crash information to check against
226
227 @rtype: bool
228 @return: True if the symptom matches, False otherwise
229 '''
230 if crashInfo.crashInstruction == None:
231
232 return False
233
234 if self.registerNames != None:
235 for register in self.registerNames:
236 if not register in crashInfo.crashInstruction:
237 return False
238
239 if self.instructionName != None:
240 if not self.instructionName.matches(crashInfo.crashInstruction):
241 return False
242
243 return True
244
252
254 '''
255 Check if the symptom matches the given crash information
256
257 @type crashInfo: CrashInfo
258 @param crashInfo: The crash information to check against
259
260 @rtype: bool
261 @return: True if the symptom matches, False otherwise
262 '''
263
264
265 if crashInfo.testcase == None:
266 return False
267
268 testLines = crashInfo.testcase.splitlines()
269
270 for line in testLines:
271 if self.output.matches(line):
272 return True
273
274 return False
275
278 '''
279 Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly.
280 '''
281 Symptom.__init__(self, obj)
282 self.functionNames = []
283
284 rawFunctionNames = JSONHelper.getArrayChecked(obj, "functionNames", True)
285
286 for fn in rawFunctionNames:
287 self.functionNames.append(StringMatch(fn))
288
290 '''
291 Check if the symptom matches the given crash information
292
293 @type crashInfo: CrashInfo
294 @param crashInfo: The crash information to check against
295
296 @rtype: bool
297 @return: True if the symptom matches, False otherwise
298 '''
299
300 return StackFramesSymptom._match(crashInfo.backtrace, self.functionNames)
301
302 - def diff(self, crashInfo):
303 if self.matches(crashInfo):
304 return (0, None)
305
306 for depth in range(1,4):
307 (bestDepth, bestGuess) = StackFramesSymptom._diff(crashInfo.backtrace, self.functionNames, 0, 1, depth)
308 if bestDepth != None:
309 guessedFunctionNames = [repr(x) for x in bestGuess]
310
311
312 while guessedFunctionNames and (guessedFunctionNames[-1] == '?' or guessedFunctionNames[-1] == '???'):
313 guessedFunctionNames.pop()
314
315 if not guessedFunctionNames:
316
317 return (None, None)
318
319 return (bestDepth, StackFramesSymptom({ "type": "stackFrames", 'functionNames' : guessedFunctionNames }))
320
321 return (None, None)
322
323 @staticmethod
324 - def _diff(stack, signatureGuess, startIdx, depth, maxDepth):
325 singleWildcardMatch = StringMatch("?")
326
327 newSignatureGuess = []
328 newSignatureGuess.extend(signatureGuess)
329
330 bestDepth = None
331 bestGuess = None
332
333 hasVariableStackLengthQuantifier = '???' in [str(x) for x in newSignatureGuess]
334
335 for idx in range(startIdx,len(newSignatureGuess)):
336 newSignatureGuess.insert(idx, singleWildcardMatch)
337
338
339 if StackFramesSymptom._match(stack, newSignatureGuess):
340 return (depth, newSignatureGuess)
341
342
343
344 if depth < maxDepth:
345 (newBestDepth, newBestGuess) = StackFramesSymptom._diff(stack, newSignatureGuess, idx, depth+1, maxDepth)
346
347 if newBestDepth != None and (bestDepth == None or newBestDepth < bestDepth):
348 bestDepth = newBestDepth
349 bestGuess = newBestGuess
350
351 newSignatureGuess.pop(idx)
352
353
354
355
356 if str(newSignatureGuess[idx]) == '?' or str(newSignatureGuess[idx]) == '???':
357 continue
358
359 newMatch = singleWildcardMatch
360 if not hasVariableStackLengthQuantifier and len(stack) > idx:
361
362
363
364 if newSignatureGuess[idx].matches(stack[idx]):
365
366 continue
367
368 if not newSignatureGuess[idx].isPCRE:
369
370
371 if stack[idx] in str(newSignatureGuess[idx]):
372
373
374
375 newMatch = StringMatch(stack[idx])
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394 origMatch = newSignatureGuess[idx]
395 newSignatureGuess[idx] = newMatch
396
397
398 if StackFramesSymptom._match(stack, newSignatureGuess):
399 return (depth, newSignatureGuess)
400
401
402
403 if depth < maxDepth:
404 (newBestDepth, newBestGuess) = StackFramesSymptom._diff(stack, newSignatureGuess, idx, depth+1, maxDepth)
405
406 if newBestDepth != None and (bestDepth == None or newBestDepth < bestDepth):
407 bestDepth = newBestDepth
408 bestGuess = newBestGuess
409
410 newSignatureGuess[idx] = origMatch
411
412 return (bestDepth, bestGuess)
413
414 @staticmethod
415 - def _match(partialStack, partialFunctionNames):
416
417 while partialFunctionNames and partialStack and str(partialFunctionNames[0]) != '?' and str(partialFunctionNames[0]) != '???':
418 if not partialFunctionNames[0].matches(partialStack[0]):
419 return False
420
421
422
423 partialStack = partialStack[1:]
424 partialFunctionNames = partialFunctionNames[1:]
425
426 if not partialFunctionNames:
427
428 return True
429
430 if str(partialFunctionNames[0]) == '?' or str(partialFunctionNames[0]) == '???':
431 if StackFramesSymptom._match(partialStack, partialFunctionNames[1:]):
432
433
434
435 return True
436 else:
437 if not partialStack:
438
439 return False
440
441 if str(partialFunctionNames[0]) == '?':
442
443 return StackFramesSymptom._match(partialStack[1:], partialFunctionNames[1:])
444 else:
445
446 return StackFramesSymptom._match(partialStack[1:], partialFunctionNames)
447 elif not partialStack:
448
449 return False
450