def test_SignatureStackFramesAlgorithmsTest(): # Do some direct matcher tests on edge cases assert StackFramesSymptom._match([], [StringMatch('???')]) assert not StackFramesSymptom._match( [], [StringMatch('???'), StringMatch('a')]) # Test the diff algorithm, test array contains: # stack, signature, expected distance, proposed signature testArray = [ (['a', 'b', 'x', 'a', 'b', 'c'], ['a', 'b', '???', 'a', 'b', 'x', 'c'], 1, ['a', 'b', '???', 'a', 'b', '?', 'c']), (['b', 'x', 'a', 'b', 'c'], ['a', 'b', '???', 'a', 'b', 'x', 'c'], 2, ['?', 'b', '???', 'a', 'b', '?', 'c']), (['b', 'x', 'a', 'd', 'x'], ['a', 'b', '???', 'a', 'b', 'x', 'c'], 3, ['?', 'b', '???', 'a', '?', 'x', '?']), ] for (stack, rawSig, expectedDepth, expectedSig) in testArray: for maxDepth in (expectedDepth, 3): (actualDepth, actualSig) = StackFramesSymptom._diff( stack, [StringMatch(x) for x in rawSig], 0, 1, maxDepth) assert expectedDepth == actualDepth assert expectedSig == [str(x) for x in actualSig]
def runTest(self): # Do some direct matcher tests on edge cases self.assertTrue(StackFramesSymptom._match([], [StringMatch('???')])) self.assertFalse( StackFramesSymptom._match( [], [StringMatch('???'), StringMatch('a')])) # Test the diff algorithm, test array contains: # stack, signature, expected distance, proposed signature testArray = [ (['a', 'b', 'x', 'a', 'b', 'c'], ['a', 'b', '???', 'a', 'b', 'x', 'c'], 1, ['a', 'b', '???', 'a', 'b', '?', 'c']), (['b', 'x', 'a', 'b', 'c'], ['a', 'b', '???', 'a', 'b', 'x', 'c'], 2, ['?', 'b', '???', 'a', 'b', '?', 'c']), (['b', 'x', 'a', 'd', 'x'], ['a', 'b', '???', 'a', 'b', 'x', 'c'], 3, ['?', 'b', '???', 'a', '?', 'x', '?']), ] for (stack, rawSig, expectedDepth, expectedSig) in testArray: for maxDepth in (expectedDepth, 3): (actualDepth, actualSig) = StackFramesSymptom._diff( stack, [StringMatch(x) for x in rawSig], 0, 1, maxDepth) self.assertEqual(expectedDepth, actualDepth) self.assertEqual(expectedSig, [str(x) for x in actualSig])
def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.output = StringMatch( JSONHelper.getObjectOrStringChecked(obj, "value", True))
def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.output = StringMatch(JSONHelper.getObjectOrStringChecked(obj, "value", True)) self.src = JSONHelper.getStringChecked(obj, "src") if self.src != None: self.src = self.src.lower() if self.src != "stderr" and self.src != "stdout" and self.src != "crashdata": raise RuntimeError("Invalid source specified: %s" % self.src)
def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.registerNames = JSONHelper.getArrayChecked(obj, "registerNames") self.instructionName = JSONHelper.getObjectOrStringChecked(obj, "instructionName") if self.instructionName != None: self.instructionName = StringMatch(self.instructionName) elif self.registerNames == None or len(self.registerNames) == 0: raise RuntimeError("Must provide at least instruction name or register names")
def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.functionName = StringMatch(JSONHelper.getNumberOrStringChecked(obj, "functionName", True)) self.frameNumber = JSONHelper.getNumberOrStringChecked(obj, "frameNumber") if self.frameNumber != None: self.frameNumber = NumberMatch(self.frameNumber) else: # Default to 0 self.frameNumber = NumberMatch(0)
class TestcaseSymptom(Symptom): def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.output = StringMatch(JSONHelper.getObjectOrStringChecked(obj, "value", True)) def matches(self, crashInfo): ''' Check if the symptom matches the given crash information @type crashInfo: CrashInfo @param crashInfo: The crash information to check against @rtype: bool @return: True if the symptom matches, False otherwise ''' # No testcase means to fail matching if crashInfo.testcase == None: return False testLines = crashInfo.testcase.splitlines() for line in testLines: if self.output.matches(line): return True return False
class StackFrameSymptom(Symptom): def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.functionName = StringMatch( JSONHelper.getNumberOrStringChecked(obj, "functionName", True)) self.frameNumber = JSONHelper.getNumberOrStringChecked( obj, "frameNumber") if self.frameNumber is not None: self.frameNumber = NumberMatch(self.frameNumber) else: # Default to 0 self.frameNumber = NumberMatch(0) def matches(self, crashInfo): ''' Check if the symptom matches the given crash information @type crashInfo: CrashInfo @param crashInfo: The crash information to check against @rtype: bool @return: True if the symptom matches, False otherwise ''' for idx in range(len(crashInfo.backtrace)): # Not the most efficient way for very long stacks with a small match area if self.frameNumber.matches(idx): if self.functionName.matches(crashInfo.backtrace[idx]): return True return False
class TestcaseSymptom(Symptom): def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.output = StringMatch( JSONHelper.getObjectOrStringChecked(obj, "value", True)) def matches(self, crashInfo): ''' Check if the symptom matches the given crash information @type crashInfo: CrashInfo @param crashInfo: The crash information to check against @rtype: bool @return: True if the symptom matches, False otherwise ''' # No testcase means to fail matching if crashInfo.testcase is None: return False testLines = crashInfo.testcase.splitlines() for line in testLines: if self.output.matches(line): return True return False
class StackFrameSymptom(Symptom): def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.functionName = StringMatch(JSONHelper.getNumberOrStringChecked(obj, "functionName", True)) self.frameNumber = JSONHelper.getNumberOrStringChecked(obj, "frameNumber") if self.frameNumber != None: self.frameNumber = NumberMatch(self.frameNumber) else: # Default to 0 self.frameNumber = NumberMatch(0) def matches(self, crashInfo): ''' Check if the symptom matches the given crash information @type crashInfo: CrashInfo @param crashInfo: The crash information to check against @rtype: bool @return: True if the symptom matches, False otherwise ''' for idx in range(len(crashInfo.backtrace)): # Not the most efficient way for very long stacks with a small match area if self.frameNumber.matches(idx): if self.functionName.matches(crashInfo.backtrace[idx]): return True return False
def _diff(stack, signatureGuess, startIdx, depth, maxDepth): singleWildcardMatch = StringMatch("?") newSignatureGuess = [] newSignatureGuess.extend(signatureGuess) bestDepth = None bestGuess = None for idx in range(startIdx, len(newSignatureGuess)): newSignatureGuess.insert(idx, singleWildcardMatch) # Check if we have a match with our modification if StackFramesSymptom._match(stack, newSignatureGuess): return (depth, newSignatureGuess) # If we don't have a match but we're not at our current depth limit, # add one more level of depth for our search. if depth < maxDepth: (newBestDepth, newBestGuess) = StackFramesSymptom._diff( stack, newSignatureGuess, idx, depth + 1, maxDepth) if newBestDepth != None and (bestDepth == None or newBestDepth < bestDepth): bestDepth = newBestDepth bestGuess = newBestGuess newSignatureGuess.pop(idx) # Now repeat the same with replacing instead of adding # unless the match at idx is a wildcard itself if str(newSignatureGuess[idx]) == '?' or str( newSignatureGuess[idx]) == '???': continue origMatch = newSignatureGuess[idx] newSignatureGuess[idx] = singleWildcardMatch # Check if we have a match with our modification if StackFramesSymptom._match(stack, newSignatureGuess): return (depth, newSignatureGuess) # If we don't have a match but we're not at our current depth limit, # add one more level of depth for our search. if depth < maxDepth: (newBestDepth, newBestGuess) = StackFramesSymptom._diff( stack, newSignatureGuess, idx, depth + 1, maxDepth) if newBestDepth != None and (bestDepth == None or newBestDepth < bestDepth): bestDepth = newBestDepth bestGuess = newBestGuess newSignatureGuess[idx] = origMatch return (bestDepth, bestGuess)
def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.functionNames = [] rawFunctionNames = JSONHelper.getArrayChecked(obj, "functionNames", True) for fn in rawFunctionNames: self.functionNames.append(StringMatch(fn))
class OutputSymptom(Symptom): def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.output = StringMatch( JSONHelper.getObjectOrStringChecked(obj, "value", True)) self.src = JSONHelper.getStringChecked(obj, "src") if self.src is not None: self.src = self.src.lower() if self.src != "stderr" and self.src != "stdout" and self.src != "crashdata": raise RuntimeError("Invalid source specified: %s" % self.src) def matches(self, crashInfo): ''' Check if the symptom matches the given crash information @type crashInfo: CrashInfo @param crashInfo: The crash information to check against @rtype: bool @return: True if the symptom matches, False otherwise ''' checkedOutput = [] if self.src is None: checkedOutput.extend(crashInfo.rawStdout) checkedOutput.extend(crashInfo.rawStderr) checkedOutput.extend(crashInfo.rawCrashData) elif (self.src == "stdout"): checkedOutput = crashInfo.rawStdout elif (self.src == "stderr"): checkedOutput = crashInfo.rawStderr else: checkedOutput = crashInfo.rawCrashData for line in reversed(checkedOutput): if self.output.matches(line): return True return False
class OutputSymptom(Symptom): def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.output = StringMatch(JSONHelper.getObjectOrStringChecked(obj, "value", True)) self.src = JSONHelper.getStringChecked(obj, "src") if self.src is not None: self.src = self.src.lower() if self.src != "stderr" and self.src != "stdout" and self.src != "crashdata": raise RuntimeError("Invalid source specified: %s" % self.src) def matches(self, crashInfo): ''' Check if the symptom matches the given crash information @type crashInfo: CrashInfo @param crashInfo: The crash information to check against @rtype: bool @return: True if the symptom matches, False otherwise ''' checkedOutput = [] if self.src is None: checkedOutput.extend(crashInfo.rawStdout) checkedOutput.extend(crashInfo.rawStderr) checkedOutput.extend(crashInfo.rawCrashData) elif (self.src == "stdout"): checkedOutput = crashInfo.rawStdout elif (self.src == "stderr"): checkedOutput = crashInfo.rawStderr else: checkedOutput = crashInfo.rawCrashData windowsSlashWorkaround = crashInfo.configuration.os == "windows" for line in reversed(checkedOutput): if self.output.matches(line, windowsSlashWorkaround=windowsSlashWorkaround): return True return False
class InstructionSymptom(Symptom): def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.registerNames = JSONHelper.getArrayChecked(obj, "registerNames") self.instructionName = JSONHelper.getObjectOrStringChecked( obj, "instructionName") if self.instructionName is not None: self.instructionName = StringMatch(self.instructionName) elif self.registerNames is None or len(self.registerNames) == 0: raise RuntimeError( "Must provide at least instruction name or register names") def matches(self, crashInfo): ''' Check if the symptom matches the given crash information @type crashInfo: CrashInfo @param crashInfo: The crash information to check against @rtype: bool @return: True if the symptom matches, False otherwise ''' if crashInfo.crashInstruction is None: # No crash instruction available, do not match return False if self.registerNames is not None: for register in self.registerNames: if register not in crashInfo.crashInstruction: return False if self.instructionName is not None: if not self.instructionName.matches(crashInfo.crashInstruction): return False return True
class InstructionSymptom(Symptom): def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.registerNames = JSONHelper.getArrayChecked(obj, "registerNames") self.instructionName = JSONHelper.getObjectOrStringChecked(obj, "instructionName") if self.instructionName != None: self.instructionName = StringMatch(self.instructionName) elif self.registerNames == None or len(self.registerNames) == 0: raise RuntimeError("Must provide at least instruction name or register names") def matches(self, crashInfo): ''' Check if the symptom matches the given crash information @type crashInfo: CrashInfo @param crashInfo: The crash information to check against @rtype: bool @return: True if the symptom matches, False otherwise ''' if crashInfo.crashInstruction == None: # No crash instruction available, do not match return False if self.registerNames != None: for register in self.registerNames: if not register in crashInfo.crashInstruction: return False if self.instructionName != None: if not self.instructionName.matches(crashInfo.crashInstruction): return False return True
def _diff(stack, signatureGuess, startIdx, depth, maxDepth): singleWildcardMatch = StringMatch("?") newSignatureGuess = [] newSignatureGuess.extend(signatureGuess) bestDepth = None bestGuess = None hasVariableStackLengthQuantifier = '???' in [ str(x) for x in newSignatureGuess ] for idx in range(startIdx, len(newSignatureGuess)): newSignatureGuess.insert(idx, singleWildcardMatch) # Check if we have a match with our modification if StackFramesSymptom._match(stack, newSignatureGuess): return (depth, newSignatureGuess) # If we don't have a match but we're not at our current depth limit, # add one more level of depth for our search. if depth < maxDepth: (newBestDepth, newBestGuess) = StackFramesSymptom._diff( stack, newSignatureGuess, idx, depth + 1, maxDepth) if newBestDepth is not None and (bestDepth is None or newBestDepth < bestDepth): bestDepth = newBestDepth bestGuess = newBestGuess newSignatureGuess.pop(idx) # Now repeat the same with replacing instead of adding # unless the match at idx is a wildcard itself if str(newSignatureGuess[idx]) == '?' or str( newSignatureGuess[idx]) == '???': continue newMatch = singleWildcardMatch if not hasVariableStackLengthQuantifier and len(stack) > idx: # We can perform some optimizations here if we have a signature that does # not contain any quantifiers that can match multiple stack frames. if newSignatureGuess[idx].matches(stack[idx]): # Our frame matches, so it doesn't make sense to try and mess with it continue if not newSignatureGuess[idx].isPCRE: # If our match is not PCRE, try some heuristics to generalize the match if stack[idx] in str(newSignatureGuess[idx]): # The stack frame is a substring of the what we try to match, # use the stack frame as new matcher to ensure a match without # using a wildcard. newMatch = StringMatch(stack[idx]) origMatch = newSignatureGuess[idx] newSignatureGuess[idx] = newMatch # Check if we have a match with our modification if StackFramesSymptom._match(stack, newSignatureGuess): return (depth, newSignatureGuess) # If we don't have a match but we're not at our current depth limit, # add one more level of depth for our search. if depth < maxDepth: (newBestDepth, newBestGuess) = StackFramesSymptom._diff( stack, newSignatureGuess, idx, depth + 1, maxDepth) if newBestDepth is not None and (bestDepth is None or newBestDepth < bestDepth): bestDepth = newBestDepth bestGuess = newBestGuess newSignatureGuess[idx] = origMatch return (bestDepth, bestGuess)
def __init__(self, obj): ''' Private constructor, called by L{Symptom.fromJSONObject}. Do not use directly. ''' Symptom.__init__(self, obj) self.output = StringMatch(JSONHelper.getObjectOrStringChecked(obj, "value", True))