xref: /aosp_15_r20/external/clang/tools/scan-view/share/Reporter.py (revision 67e74705e28f6214e480b399dd47ea732279e315)
1*67e74705SXin Li"""Methods for reporting bugs."""
2*67e74705SXin Li
3*67e74705SXin Liimport subprocess, sys, os
4*67e74705SXin Li
5*67e74705SXin Li__all__ = ['ReportFailure', 'BugReport', 'getReporters']
6*67e74705SXin Li
7*67e74705SXin Li#
8*67e74705SXin Li
9*67e74705SXin Liclass ReportFailure(Exception):
10*67e74705SXin Li    """Generic exception for failures in bug reporting."""
11*67e74705SXin Li    def __init__(self, value):
12*67e74705SXin Li        self.value = value
13*67e74705SXin Li
14*67e74705SXin Li# Collect information about a bug.
15*67e74705SXin Li
16*67e74705SXin Liclass BugReport:
17*67e74705SXin Li    def __init__(self, title, description, files):
18*67e74705SXin Li        self.title = title
19*67e74705SXin Li        self.description = description
20*67e74705SXin Li        self.files = files
21*67e74705SXin Li
22*67e74705SXin Li# Reporter interfaces.
23*67e74705SXin Li
24*67e74705SXin Liimport os
25*67e74705SXin Li
26*67e74705SXin Liimport email, mimetypes, smtplib
27*67e74705SXin Lifrom email import encoders
28*67e74705SXin Lifrom email.message import Message
29*67e74705SXin Lifrom email.mime.base import MIMEBase
30*67e74705SXin Lifrom email.mime.multipart import MIMEMultipart
31*67e74705SXin Lifrom email.mime.text import MIMEText
32*67e74705SXin Li
33*67e74705SXin Li#===------------------------------------------------------------------------===#
34*67e74705SXin Li# ReporterParameter
35*67e74705SXin Li#===------------------------------------------------------------------------===#
36*67e74705SXin Li
37*67e74705SXin Liclass ReporterParameter:
38*67e74705SXin Li  def __init__(self, n):
39*67e74705SXin Li    self.name = n
40*67e74705SXin Li  def getName(self):
41*67e74705SXin Li    return self.name
42*67e74705SXin Li  def getValue(self,r,bugtype,getConfigOption):
43*67e74705SXin Li     return getConfigOption(r.getName(),self.getName())
44*67e74705SXin Li  def saveConfigValue(self):
45*67e74705SXin Li    return True
46*67e74705SXin Li
47*67e74705SXin Liclass TextParameter (ReporterParameter):
48*67e74705SXin Li  def getHTML(self,r,bugtype,getConfigOption):
49*67e74705SXin Li    return """\
50*67e74705SXin Li<tr>
51*67e74705SXin Li<td class="form_clabel">%s:</td>
52*67e74705SXin Li<td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
53*67e74705SXin Li</tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption))
54*67e74705SXin Li
55*67e74705SXin Liclass SelectionParameter (ReporterParameter):
56*67e74705SXin Li  def __init__(self, n, values):
57*67e74705SXin Li    ReporterParameter.__init__(self,n)
58*67e74705SXin Li    self.values = values
59*67e74705SXin Li
60*67e74705SXin Li  def getHTML(self,r,bugtype,getConfigOption):
61*67e74705SXin Li    default = self.getValue(r,bugtype,getConfigOption)
62*67e74705SXin Li    return """\
63*67e74705SXin Li<tr>
64*67e74705SXin Li<td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
65*67e74705SXin Li%s
66*67e74705SXin Li</select></td>"""%(self.getName(),r.getName(),self.getName(),'\n'.join(["""\
67*67e74705SXin Li<option value="%s"%s>%s</option>"""%(o[0],
68*67e74705SXin Li                                     o[0] == default and ' selected="selected"' or '',
69*67e74705SXin Li                                     o[1]) for o in self.values]))
70*67e74705SXin Li
71*67e74705SXin Li#===------------------------------------------------------------------------===#
72*67e74705SXin Li# Reporters
73*67e74705SXin Li#===------------------------------------------------------------------------===#
74*67e74705SXin Li
75*67e74705SXin Liclass EmailReporter:
76*67e74705SXin Li    def getName(self):
77*67e74705SXin Li        return 'Email'
78*67e74705SXin Li
79*67e74705SXin Li    def getParameters(self):
80*67e74705SXin Li        return map(lambda x:TextParameter(x),['To', 'From', 'SMTP Server', 'SMTP Port'])
81*67e74705SXin Li
82*67e74705SXin Li    # Lifted from python email module examples.
83*67e74705SXin Li    def attachFile(self, outer, path):
84*67e74705SXin Li        # Guess the content type based on the file's extension.  Encoding
85*67e74705SXin Li        # will be ignored, although we should check for simple things like
86*67e74705SXin Li        # gzip'd or compressed files.
87*67e74705SXin Li        ctype, encoding = mimetypes.guess_type(path)
88*67e74705SXin Li        if ctype is None or encoding is not None:
89*67e74705SXin Li            # No guess could be made, or the file is encoded (compressed), so
90*67e74705SXin Li            # use a generic bag-of-bits type.
91*67e74705SXin Li            ctype = 'application/octet-stream'
92*67e74705SXin Li        maintype, subtype = ctype.split('/', 1)
93*67e74705SXin Li        if maintype == 'text':
94*67e74705SXin Li            fp = open(path)
95*67e74705SXin Li            # Note: we should handle calculating the charset
96*67e74705SXin Li            msg = MIMEText(fp.read(), _subtype=subtype)
97*67e74705SXin Li            fp.close()
98*67e74705SXin Li        else:
99*67e74705SXin Li            fp = open(path, 'rb')
100*67e74705SXin Li            msg = MIMEBase(maintype, subtype)
101*67e74705SXin Li            msg.set_payload(fp.read())
102*67e74705SXin Li            fp.close()
103*67e74705SXin Li            # Encode the payload using Base64
104*67e74705SXin Li            encoders.encode_base64(msg)
105*67e74705SXin Li        # Set the filename parameter
106*67e74705SXin Li        msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
107*67e74705SXin Li        outer.attach(msg)
108*67e74705SXin Li
109*67e74705SXin Li    def fileReport(self, report, parameters):
110*67e74705SXin Li        mainMsg = """\
111*67e74705SXin LiBUG REPORT
112*67e74705SXin Li---
113*67e74705SXin LiTitle: %s
114*67e74705SXin LiDescription: %s
115*67e74705SXin Li"""%(report.title, report.description)
116*67e74705SXin Li
117*67e74705SXin Li        if not parameters.get('To'):
118*67e74705SXin Li            raise ReportFailure('No "To" address specified.')
119*67e74705SXin Li        if not parameters.get('From'):
120*67e74705SXin Li            raise ReportFailure('No "From" address specified.')
121*67e74705SXin Li
122*67e74705SXin Li        msg = MIMEMultipart()
123*67e74705SXin Li        msg['Subject'] = 'BUG REPORT: %s'%(report.title)
124*67e74705SXin Li        # FIXME: Get config parameters
125*67e74705SXin Li        msg['To'] = parameters.get('To')
126*67e74705SXin Li        msg['From'] = parameters.get('From')
127*67e74705SXin Li        msg.preamble = mainMsg
128*67e74705SXin Li
129*67e74705SXin Li        msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
130*67e74705SXin Li        for file in report.files:
131*67e74705SXin Li            self.attachFile(msg, file)
132*67e74705SXin Li
133*67e74705SXin Li        try:
134*67e74705SXin Li            s = smtplib.SMTP(host=parameters.get('SMTP Server'),
135*67e74705SXin Li                             port=parameters.get('SMTP Port'))
136*67e74705SXin Li            s.sendmail(msg['From'], msg['To'], msg.as_string())
137*67e74705SXin Li            s.close()
138*67e74705SXin Li        except:
139*67e74705SXin Li            raise ReportFailure('Unable to send message via SMTP.')
140*67e74705SXin Li
141*67e74705SXin Li        return "Message sent!"
142*67e74705SXin Li
143*67e74705SXin Liclass BugzillaReporter:
144*67e74705SXin Li    def getName(self):
145*67e74705SXin Li        return 'Bugzilla'
146*67e74705SXin Li
147*67e74705SXin Li    def getParameters(self):
148*67e74705SXin Li        return map(lambda x:TextParameter(x),['URL','Product'])
149*67e74705SXin Li
150*67e74705SXin Li    def fileReport(self, report, parameters):
151*67e74705SXin Li        raise NotImplementedError
152*67e74705SXin Li
153*67e74705SXin Li
154*67e74705SXin Liclass RadarClassificationParameter(SelectionParameter):
155*67e74705SXin Li  def __init__(self):
156*67e74705SXin Li    SelectionParameter.__init__(self,"Classification",
157*67e74705SXin Li            [['1', 'Security'], ['2', 'Crash/Hang/Data Loss'],
158*67e74705SXin Li             ['3', 'Performance'], ['4', 'UI/Usability'],
159*67e74705SXin Li             ['6', 'Serious Bug'], ['7', 'Other']])
160*67e74705SXin Li
161*67e74705SXin Li  def saveConfigValue(self):
162*67e74705SXin Li    return False
163*67e74705SXin Li
164*67e74705SXin Li  def getValue(self,r,bugtype,getConfigOption):
165*67e74705SXin Li    if bugtype.find("leak") != -1:
166*67e74705SXin Li      return '3'
167*67e74705SXin Li    elif bugtype.find("dereference") != -1:
168*67e74705SXin Li      return '2'
169*67e74705SXin Li    elif bugtype.find("missing ivar release") != -1:
170*67e74705SXin Li      return '3'
171*67e74705SXin Li    else:
172*67e74705SXin Li      return '7'
173*67e74705SXin Li
174*67e74705SXin Liclass RadarReporter:
175*67e74705SXin Li    @staticmethod
176*67e74705SXin Li    def isAvailable():
177*67e74705SXin Li        # FIXME: Find this .scpt better
178*67e74705SXin Li        path = os.path.join(os.path.dirname(__file__),'../share/scan-view/GetRadarVersion.scpt')
179*67e74705SXin Li        try:
180*67e74705SXin Li          p = subprocess.Popen(['osascript',path],
181*67e74705SXin Li          stdout=subprocess.PIPE, stderr=subprocess.PIPE)
182*67e74705SXin Li        except:
183*67e74705SXin Li            return False
184*67e74705SXin Li        data,err = p.communicate()
185*67e74705SXin Li        res = p.wait()
186*67e74705SXin Li        # FIXME: Check version? Check for no errors?
187*67e74705SXin Li        return res == 0
188*67e74705SXin Li
189*67e74705SXin Li    def getName(self):
190*67e74705SXin Li        return 'Radar'
191*67e74705SXin Li
192*67e74705SXin Li    def getParameters(self):
193*67e74705SXin Li        return [ TextParameter('Component'), TextParameter('Component Version'),
194*67e74705SXin Li                 RadarClassificationParameter() ]
195*67e74705SXin Li
196*67e74705SXin Li    def fileReport(self, report, parameters):
197*67e74705SXin Li        component = parameters.get('Component', '')
198*67e74705SXin Li        componentVersion = parameters.get('Component Version', '')
199*67e74705SXin Li        classification = parameters.get('Classification', '')
200*67e74705SXin Li        personID = ""
201*67e74705SXin Li        diagnosis = ""
202*67e74705SXin Li        config = ""
203*67e74705SXin Li
204*67e74705SXin Li        if not component.strip():
205*67e74705SXin Li            component = 'Bugs found by clang Analyzer'
206*67e74705SXin Li        if not componentVersion.strip():
207*67e74705SXin Li            componentVersion = 'X'
208*67e74705SXin Li
209*67e74705SXin Li        script = os.path.join(os.path.dirname(__file__),'../share/scan-view/FileRadar.scpt')
210*67e74705SXin Li        args = ['osascript', script, component, componentVersion, classification, personID, report.title,
211*67e74705SXin Li                report.description, diagnosis, config] + map(os.path.abspath, report.files)
212*67e74705SXin Li#        print >>sys.stderr, args
213*67e74705SXin Li        try:
214*67e74705SXin Li          p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
215*67e74705SXin Li        except:
216*67e74705SXin Li            raise ReportFailure("Unable to file radar (AppleScript failure).")
217*67e74705SXin Li        data, err = p.communicate()
218*67e74705SXin Li        res = p.wait()
219*67e74705SXin Li
220*67e74705SXin Li        if res:
221*67e74705SXin Li            raise ReportFailure("Unable to file radar (AppleScript failure).")
222*67e74705SXin Li
223*67e74705SXin Li        try:
224*67e74705SXin Li            values = eval(data)
225*67e74705SXin Li        except:
226*67e74705SXin Li            raise ReportFailure("Unable to process radar results.")
227*67e74705SXin Li
228*67e74705SXin Li        # We expect (int: bugID, str: message)
229*67e74705SXin Li        if len(values) != 2 or not isinstance(values[0], int):
230*67e74705SXin Li            raise ReportFailure("Unable to process radar results.")
231*67e74705SXin Li
232*67e74705SXin Li        bugID,message = values
233*67e74705SXin Li        bugID = int(bugID)
234*67e74705SXin Li
235*67e74705SXin Li        if not bugID:
236*67e74705SXin Li            raise ReportFailure(message)
237*67e74705SXin Li
238*67e74705SXin Li        return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID)
239*67e74705SXin Li
240*67e74705SXin Li###
241*67e74705SXin Li
242*67e74705SXin Lidef getReporters():
243*67e74705SXin Li    reporters = []
244*67e74705SXin Li    if RadarReporter.isAvailable():
245*67e74705SXin Li        reporters.append(RadarReporter())
246*67e74705SXin Li    reporters.append(EmailReporter())
247*67e74705SXin Li    return reporters
248*67e74705SXin Li
249