1 '''
2 Crash Signature
3
4 Represents a crash signature as specified in https://wiki.mozilla.org/Security/CrashSignatures
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 import json
18 import difflib
19 from FTB.Signatures import JSONHelper
20 from FTB.Signatures.Symptom import Symptom, TestcaseSymptom, StackFramesSymptom
21 import FTB.Signatures
22
23 from collections import OrderedDict
27 '''
28 Constructor
29
30 @type rawSignature: string
31 @param rawSignature: A JSON-formatted string representing the crash signature
32 '''
33
34
35
36
37
38
39 self.rawSignature = rawSignature
40 self.symptoms = []
41
42 try:
43 obj = json.loads(rawSignature, object_pairs_hook=OrderedDict)
44 except ValueError, e:
45 raise RuntimeError("Invalid JSON: %s" % e)
46
47
48 if "symptoms" in obj:
49 symptoms = JSONHelper.getArrayChecked(obj, "symptoms", True)
50 if len(symptoms) == 0:
51 raise RuntimeError("Signature must have at least one symptom.")
52
53 for rawSymptomsObj in symptoms:
54 self.symptoms.append(Symptom.fromJSONObject(rawSymptomsObj))
55 else:
56 raise RuntimeError('Missing mandatory top-level key "symptoms".')
57
58
59 self.platforms = JSONHelper.getArrayChecked(obj, "platforms")
60 self.operatingSystems = JSONHelper.getArrayChecked(obj, "operatingSystems")
61 self.products = JSONHelper.getArrayChecked(obj, "products")
62
63 @staticmethod
65 with open(signatureFile, 'r') as sigFd:
66 return CrashSignature(sigFd.read())
67
69 return self.rawSignature
70
72 '''
73 Match this signature against the given crash information
74
75 @type crashInfo: CrashInfo
76 @param crashInfo: The crash info to match the signature against
77
78 @rtype: bool
79 @return: True if the signature matches, False otherwise
80 '''
81 if self.platforms != None and not crashInfo.configuration.platform in self.platforms:
82 return False
83
84 if self.operatingSystems != None and not crashInfo.configuration.os in self.operatingSystems:
85 return False
86
87 if self.products != None and not crashInfo.configuration.product in self.products:
88 return False
89
90 for symptom in self.symptoms:
91 if not symptom.matches(crashInfo):
92 return False
93
94 return True
95
97 '''
98 Check if the signature requires a testcase to match.
99
100 This method can be used to avoid attaching a testcase to the crashInfo
101 before matching, avoiding unnecessary I/O on testcase files.
102
103 @rtype: bool
104 @return: True if the signature requires a testcase to match
105 '''
106 for symptom in self.symptoms:
107 if isinstance(symptom, TestcaseSymptom):
108 return True
109
110 return False
111
113 distance = 0
114
115 for symptom in self.symptoms:
116 if isinstance(symptom, StackFramesSymptom):
117 symptomDistance = symptom.diff(crashInfo)[0]
118 if symptomDistance != None:
119 distance += symptomDistance
120 else:
121
122 distance += len(symptom.functionNames)
123 else:
124 if not symptom.matches(crashInfo):
125 distance +=1
126
127 return distance
128
129 - def fit(self, crashInfo):
130 sigObj = {}
131 sigSymptoms = []
132
133 sigObj['symptoms'] = sigSymptoms
134
135 if self.platforms:
136 sigObj['platforms'] = self.platforms
137
138 if self.operatingSystems:
139 sigObj['operatingSystems'] = self.operatingSystems
140
141 if self.products:
142 sigObj['products'] = self.products
143
144 symptomsDiff = self.getSymptomsDiff(crashInfo)
145
146 for symptomDiff in symptomsDiff:
147 if symptomDiff['offending']:
148 if 'proposed' in symptomDiff:
149 sigSymptoms.append(symptomDiff['proposed'].jsonobj)
150 else:
151 sigSymptoms.append(symptomDiff['symptom'].jsonobj)
152
153 if not sigSymptoms:
154 return None
155
156 return CrashSignature(json.dumps(sigObj, indent=2))
157
159 symptomsDiff = []
160 for symptom in self.symptoms:
161 if symptom.matches(crashInfo):
162 symptomsDiff.append({ 'offending' : False, 'symptom' : symptom })
163 else:
164
165
166
167 if isinstance(symptom, StackFramesSymptom):
168 proposedSymptom = symptom.diff(crashInfo)[1]
169 if proposedSymptom:
170 symptomsDiff.append({ 'offending' : True, 'symptom' : symptom, 'proposed' : proposedSymptom })
171 continue
172
173 symptomsDiff.append({ 'offending' : True, 'symptom' : symptom })
174 return symptomsDiff
175
177 diffTuples = []
178
179 newRawCrashSignature = self.fit(crashInfo)
180 oldLines = self.rawSignature.splitlines()
181 newLines = []
182 if newRawCrashSignature:
183 newLines = newRawCrashSignature.rawSignature.splitlines()
184 context = max(len(oldLines),len(newLines))
185
186 signatureDiff = difflib.unified_diff(oldLines, newLines, n=context)
187
188 for diffLine in signatureDiff:
189 if diffLine.startswith('+++') or diffLine.startswith('---') or diffLine.startswith('@@') or not diffLine.strip():
190 continue
191
192 diffTuples.append((diffLine[0],diffLine[1:]))
193
194 return diffTuples
195