xref: /aosp_15_r20/external/clang/tools/scan-view/share/ScanView.py (revision 67e74705e28f6214e480b399dd47ea732279e315)
1*67e74705SXin Liimport BaseHTTPServer
2*67e74705SXin Liimport SimpleHTTPServer
3*67e74705SXin Liimport os
4*67e74705SXin Liimport sys
5*67e74705SXin Liimport urllib, urlparse
6*67e74705SXin Liimport posixpath
7*67e74705SXin Liimport StringIO
8*67e74705SXin Liimport re
9*67e74705SXin Liimport shutil
10*67e74705SXin Liimport threading
11*67e74705SXin Liimport time
12*67e74705SXin Liimport socket
13*67e74705SXin Liimport itertools
14*67e74705SXin Li
15*67e74705SXin Liimport Reporter
16*67e74705SXin Liimport ConfigParser
17*67e74705SXin Li
18*67e74705SXin Li###
19*67e74705SXin Li# Various patterns matched or replaced by server.
20*67e74705SXin Li
21*67e74705SXin LikReportFileRE = re.compile('(.*/)?report-(.*)\\.html')
22*67e74705SXin Li
23*67e74705SXin LikBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
24*67e74705SXin Li
25*67e74705SXin Li#  <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" -->
26*67e74705SXin Li
27*67e74705SXin LikReportCrashEntryRE = re.compile('<!-- REPORTPROBLEM (.*?)-->')
28*67e74705SXin LikReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"')
29*67e74705SXin Li
30*67e74705SXin LikReportReplacements = []
31*67e74705SXin Li
32*67e74705SXin Li# Add custom javascript.
33*67e74705SXin LikReportReplacements.append((re.compile('<!-- SUMMARYENDHEAD -->'), """\
34*67e74705SXin Li<script language="javascript" type="text/javascript">
35*67e74705SXin Lifunction load(url) {
36*67e74705SXin Li  if (window.XMLHttpRequest) {
37*67e74705SXin Li    req = new XMLHttpRequest();
38*67e74705SXin Li  } else if (window.ActiveXObject) {
39*67e74705SXin Li    req = new ActiveXObject("Microsoft.XMLHTTP");
40*67e74705SXin Li  }
41*67e74705SXin Li  if (req != undefined) {
42*67e74705SXin Li    req.open("GET", url, true);
43*67e74705SXin Li    req.send("");
44*67e74705SXin Li  }
45*67e74705SXin Li}
46*67e74705SXin Li</script>"""))
47*67e74705SXin Li
48*67e74705SXin Li# Insert additional columns.
49*67e74705SXin LikReportReplacements.append((re.compile('<!-- REPORTBUGCOL -->'),
50*67e74705SXin Li                            '<td></td><td></td>'))
51*67e74705SXin Li
52*67e74705SXin Li# Insert report bug and open file links.
53*67e74705SXin LikReportReplacements.append((re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'),
54*67e74705SXin Li                            ('<td class="Button"><a href="report/\\1">Report Bug</a></td>' +
55*67e74705SXin Li                             '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>')))
56*67e74705SXin Li
57*67e74705SXin LikReportReplacements.append((re.compile('<!-- REPORTHEADER -->'),
58*67e74705SXin Li                                       '<h3><a href="/">Summary</a> > Report %(report)s</h3>'))
59*67e74705SXin Li
60*67e74705SXin LikReportReplacements.append((re.compile('<!-- REPORTSUMMARYEXTRA -->'),
61*67e74705SXin Li                            '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>'))
62*67e74705SXin Li
63*67e74705SXin Li# Insert report crashes link.
64*67e74705SXin Li
65*67e74705SXin Li# Disabled for the time being until we decide exactly when this should
66*67e74705SXin Li# be enabled. Also the radar reporter needs to be fixed to report
67*67e74705SXin Li# multiple files.
68*67e74705SXin Li
69*67e74705SXin Li#kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'),
70*67e74705SXin Li#                            '<br>These files will automatically be attached to ' +
71*67e74705SXin Li#                            'reports filed here: <a href="report_crashes">Report Crashes</a>.'))
72*67e74705SXin Li
73*67e74705SXin Li###
74*67e74705SXin Li# Other simple parameters
75*67e74705SXin Li
76*67e74705SXin LikShare = posixpath.join(posixpath.dirname(__file__), '../share/scan-view')
77*67e74705SXin LikConfigPath = os.path.expanduser('~/.scanview.cfg')
78*67e74705SXin Li
79*67e74705SXin Li###
80*67e74705SXin Li
81*67e74705SXin Li__version__ = "0.1"
82*67e74705SXin Li
83*67e74705SXin Li__all__ = ["create_server"]
84*67e74705SXin Li
85*67e74705SXin Liclass ReporterThread(threading.Thread):
86*67e74705SXin Li    def __init__(self, report, reporter, parameters, server):
87*67e74705SXin Li        threading.Thread.__init__(self)
88*67e74705SXin Li        self.report = report
89*67e74705SXin Li        self.server = server
90*67e74705SXin Li        self.reporter = reporter
91*67e74705SXin Li        self.parameters = parameters
92*67e74705SXin Li        self.success = False
93*67e74705SXin Li        self.status = None
94*67e74705SXin Li
95*67e74705SXin Li    def run(self):
96*67e74705SXin Li        result = None
97*67e74705SXin Li        try:
98*67e74705SXin Li            if self.server.options.debug:
99*67e74705SXin Li                print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],)
100*67e74705SXin Li            self.status = self.reporter.fileReport(self.report, self.parameters)
101*67e74705SXin Li            self.success = True
102*67e74705SXin Li            time.sleep(3)
103*67e74705SXin Li            if self.server.options.debug:
104*67e74705SXin Li                print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
105*67e74705SXin Li        except Reporter.ReportFailure,e:
106*67e74705SXin Li            self.status = e.value
107*67e74705SXin Li        except Exception,e:
108*67e74705SXin Li            s = StringIO.StringIO()
109*67e74705SXin Li            import traceback
110*67e74705SXin Li            print >>s,'<b>Unhandled Exception</b><br><pre>'
111*67e74705SXin Li            traceback.print_exc(e,file=s)
112*67e74705SXin Li            print >>s,'</pre>'
113*67e74705SXin Li            self.status = s.getvalue()
114*67e74705SXin Li
115*67e74705SXin Liclass ScanViewServer(BaseHTTPServer.HTTPServer):
116*67e74705SXin Li    def __init__(self, address, handler, root, reporters, options):
117*67e74705SXin Li        BaseHTTPServer.HTTPServer.__init__(self, address, handler)
118*67e74705SXin Li        self.root = root
119*67e74705SXin Li        self.reporters = reporters
120*67e74705SXin Li        self.options = options
121*67e74705SXin Li        self.halted = False
122*67e74705SXin Li        self.config = None
123*67e74705SXin Li        self.load_config()
124*67e74705SXin Li
125*67e74705SXin Li    def load_config(self):
126*67e74705SXin Li        self.config = ConfigParser.RawConfigParser()
127*67e74705SXin Li
128*67e74705SXin Li        # Add defaults
129*67e74705SXin Li        self.config.add_section('ScanView')
130*67e74705SXin Li        for r in self.reporters:
131*67e74705SXin Li            self.config.add_section(r.getName())
132*67e74705SXin Li            for p in r.getParameters():
133*67e74705SXin Li              if p.saveConfigValue():
134*67e74705SXin Li                self.config.set(r.getName(), p.getName(), '')
135*67e74705SXin Li
136*67e74705SXin Li        # Ignore parse errors
137*67e74705SXin Li        try:
138*67e74705SXin Li            self.config.read([kConfigPath])
139*67e74705SXin Li        except:
140*67e74705SXin Li            pass
141*67e74705SXin Li
142*67e74705SXin Li        # Save on exit
143*67e74705SXin Li        import atexit
144*67e74705SXin Li        atexit.register(lambda: self.save_config())
145*67e74705SXin Li
146*67e74705SXin Li    def save_config(self):
147*67e74705SXin Li        # Ignore errors (only called on exit).
148*67e74705SXin Li        try:
149*67e74705SXin Li            f = open(kConfigPath,'w')
150*67e74705SXin Li            self.config.write(f)
151*67e74705SXin Li            f.close()
152*67e74705SXin Li        except:
153*67e74705SXin Li            pass
154*67e74705SXin Li
155*67e74705SXin Li    def halt(self):
156*67e74705SXin Li        self.halted = True
157*67e74705SXin Li        if self.options.debug:
158*67e74705SXin Li            print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],)
159*67e74705SXin Li
160*67e74705SXin Li    def serve_forever(self):
161*67e74705SXin Li        while not self.halted:
162*67e74705SXin Li            if self.options.debug > 1:
163*67e74705SXin Li                print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],)
164*67e74705SXin Li            try:
165*67e74705SXin Li                self.handle_request()
166*67e74705SXin Li            except OSError,e:
167*67e74705SXin Li                print 'OSError',e.errno
168*67e74705SXin Li
169*67e74705SXin Li    def finish_request(self, request, client_address):
170*67e74705SXin Li        if self.options.autoReload:
171*67e74705SXin Li            import ScanView
172*67e74705SXin Li            self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler
173*67e74705SXin Li        BaseHTTPServer.HTTPServer.finish_request(self, request, client_address)
174*67e74705SXin Li
175*67e74705SXin Li    def handle_error(self, request, client_address):
176*67e74705SXin Li        # Ignore socket errors
177*67e74705SXin Li        info = sys.exc_info()
178*67e74705SXin Li        if info and isinstance(info[1], socket.error):
179*67e74705SXin Li            if self.options.debug > 1:
180*67e74705SXin Li                print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],)
181*67e74705SXin Li            return
182*67e74705SXin Li        BaseHTTPServer.HTTPServer.handle_error(self, request, client_address)
183*67e74705SXin Li
184*67e74705SXin Li# Borrowed from Quixote, with simplifications.
185*67e74705SXin Lidef parse_query(qs, fields=None):
186*67e74705SXin Li    if fields is None:
187*67e74705SXin Li        fields = {}
188*67e74705SXin Li    for chunk in filter(None, qs.split('&')):
189*67e74705SXin Li        if '=' not in chunk:
190*67e74705SXin Li            name = chunk
191*67e74705SXin Li            value = ''
192*67e74705SXin Li        else:
193*67e74705SXin Li            name, value = chunk.split('=', 1)
194*67e74705SXin Li        name = urllib.unquote(name.replace('+', ' '))
195*67e74705SXin Li        value = urllib.unquote(value.replace('+', ' '))
196*67e74705SXin Li        item = fields.get(name)
197*67e74705SXin Li        if item is None:
198*67e74705SXin Li            fields[name] = [value]
199*67e74705SXin Li        else:
200*67e74705SXin Li            item.append(value)
201*67e74705SXin Li    return fields
202*67e74705SXin Li
203*67e74705SXin Liclass ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
204*67e74705SXin Li    server_version = "ScanViewServer/" + __version__
205*67e74705SXin Li    dynamic_mtime = time.time()
206*67e74705SXin Li
207*67e74705SXin Li    def do_HEAD(self):
208*67e74705SXin Li        try:
209*67e74705SXin Li            SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
210*67e74705SXin Li        except Exception,e:
211*67e74705SXin Li            self.handle_exception(e)
212*67e74705SXin Li
213*67e74705SXin Li    def do_GET(self):
214*67e74705SXin Li        try:
215*67e74705SXin Li            SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
216*67e74705SXin Li        except Exception,e:
217*67e74705SXin Li            self.handle_exception(e)
218*67e74705SXin Li
219*67e74705SXin Li    def do_POST(self):
220*67e74705SXin Li        """Serve a POST request."""
221*67e74705SXin Li        try:
222*67e74705SXin Li            length = self.headers.getheader('content-length') or "0"
223*67e74705SXin Li            try:
224*67e74705SXin Li                length = int(length)
225*67e74705SXin Li            except:
226*67e74705SXin Li                length = 0
227*67e74705SXin Li            content = self.rfile.read(length)
228*67e74705SXin Li            fields = parse_query(content)
229*67e74705SXin Li            f = self.send_head(fields)
230*67e74705SXin Li            if f:
231*67e74705SXin Li                self.copyfile(f, self.wfile)
232*67e74705SXin Li                f.close()
233*67e74705SXin Li        except Exception,e:
234*67e74705SXin Li            self.handle_exception(e)
235*67e74705SXin Li
236*67e74705SXin Li    def log_message(self, format, *args):
237*67e74705SXin Li        if self.server.options.debug:
238*67e74705SXin Li            sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
239*67e74705SXin Li                             (sys.argv[0],
240*67e74705SXin Li                              self.address_string(),
241*67e74705SXin Li                              self.log_date_time_string(),
242*67e74705SXin Li                              format%args))
243*67e74705SXin Li
244*67e74705SXin Li    def load_report(self, report):
245*67e74705SXin Li        path = os.path.join(self.server.root, 'report-%s.html'%report)
246*67e74705SXin Li        data = open(path).read()
247*67e74705SXin Li        keys = {}
248*67e74705SXin Li        for item in kBugKeyValueRE.finditer(data):
249*67e74705SXin Li            k,v = item.groups()
250*67e74705SXin Li            keys[k] = v
251*67e74705SXin Li        return keys
252*67e74705SXin Li
253*67e74705SXin Li    def load_crashes(self):
254*67e74705SXin Li        path = posixpath.join(self.server.root, 'index.html')
255*67e74705SXin Li        data = open(path).read()
256*67e74705SXin Li        problems = []
257*67e74705SXin Li        for item in kReportCrashEntryRE.finditer(data):
258*67e74705SXin Li            fieldData = item.group(1)
259*67e74705SXin Li            fields = dict([i.groups() for i in
260*67e74705SXin Li                           kReportCrashEntryKeyValueRE.finditer(fieldData)])
261*67e74705SXin Li            problems.append(fields)
262*67e74705SXin Li        return problems
263*67e74705SXin Li
264*67e74705SXin Li    def handle_exception(self, exc):
265*67e74705SXin Li        import traceback
266*67e74705SXin Li        s = StringIO.StringIO()
267*67e74705SXin Li        print >>s, "INTERNAL ERROR\n"
268*67e74705SXin Li        traceback.print_exc(exc, s)
269*67e74705SXin Li        f = self.send_string(s.getvalue(), 'text/plain')
270*67e74705SXin Li        if f:
271*67e74705SXin Li            self.copyfile(f, self.wfile)
272*67e74705SXin Li            f.close()
273*67e74705SXin Li
274*67e74705SXin Li    def get_scalar_field(self, name):
275*67e74705SXin Li        if name in self.fields:
276*67e74705SXin Li            return self.fields[name][0]
277*67e74705SXin Li        else:
278*67e74705SXin Li            return None
279*67e74705SXin Li
280*67e74705SXin Li    def submit_bug(self, c):
281*67e74705SXin Li        title = self.get_scalar_field('title')
282*67e74705SXin Li        description = self.get_scalar_field('description')
283*67e74705SXin Li        report = self.get_scalar_field('report')
284*67e74705SXin Li        reporterIndex = self.get_scalar_field('reporter')
285*67e74705SXin Li        files = []
286*67e74705SXin Li        for fileID in self.fields.get('files',[]):
287*67e74705SXin Li            try:
288*67e74705SXin Li                i = int(fileID)
289*67e74705SXin Li            except:
290*67e74705SXin Li                i = None
291*67e74705SXin Li            if i is None or i<0 or i>=len(c.files):
292*67e74705SXin Li                return (False, 'Invalid file ID')
293*67e74705SXin Li            files.append(c.files[i])
294*67e74705SXin Li
295*67e74705SXin Li        if not title:
296*67e74705SXin Li            return (False, "Missing title.")
297*67e74705SXin Li        if not description:
298*67e74705SXin Li            return (False, "Missing description.")
299*67e74705SXin Li        try:
300*67e74705SXin Li            reporterIndex = int(reporterIndex)
301*67e74705SXin Li        except:
302*67e74705SXin Li            return (False, "Invalid report method.")
303*67e74705SXin Li
304*67e74705SXin Li        # Get the reporter and parameters.
305*67e74705SXin Li        reporter = self.server.reporters[reporterIndex]
306*67e74705SXin Li        parameters = {}
307*67e74705SXin Li        for o in reporter.getParameters():
308*67e74705SXin Li            name = '%s_%s'%(reporter.getName(),o.getName())
309*67e74705SXin Li            if name not in self.fields:
310*67e74705SXin Li                return (False,
311*67e74705SXin Li                        'Missing field "%s" for %s report method.'%(name,
312*67e74705SXin Li                                                                    reporter.getName()))
313*67e74705SXin Li            parameters[o.getName()] = self.get_scalar_field(name)
314*67e74705SXin Li
315*67e74705SXin Li        # Update config defaults.
316*67e74705SXin Li        if report != 'None':
317*67e74705SXin Li            self.server.config.set('ScanView', 'reporter', reporterIndex)
318*67e74705SXin Li            for o in reporter.getParameters():
319*67e74705SXin Li              if o.saveConfigValue():
320*67e74705SXin Li                name = o.getName()
321*67e74705SXin Li                self.server.config.set(reporter.getName(), name, parameters[name])
322*67e74705SXin Li
323*67e74705SXin Li        # Create the report.
324*67e74705SXin Li        bug = Reporter.BugReport(title, description, files)
325*67e74705SXin Li
326*67e74705SXin Li        # Kick off a reporting thread.
327*67e74705SXin Li        t = ReporterThread(bug, reporter, parameters, self.server)
328*67e74705SXin Li        t.start()
329*67e74705SXin Li
330*67e74705SXin Li        # Wait for thread to die...
331*67e74705SXin Li        while t.isAlive():
332*67e74705SXin Li            time.sleep(.25)
333*67e74705SXin Li        submitStatus = t.status
334*67e74705SXin Li
335*67e74705SXin Li        return (t.success, t.status)
336*67e74705SXin Li
337*67e74705SXin Li    def send_report_submit(self):
338*67e74705SXin Li        report = self.get_scalar_field('report')
339*67e74705SXin Li        c = self.get_report_context(report)
340*67e74705SXin Li        if c.reportSource is None:
341*67e74705SXin Li            reportingFor = "Report Crashes > "
342*67e74705SXin Li            fileBug = """\
343*67e74705SXin Li<a href="/report_crashes">File Bug</a> > """%locals()
344*67e74705SXin Li        else:
345*67e74705SXin Li            reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource,
346*67e74705SXin Li                                                                   report)
347*67e74705SXin Li            fileBug = '<a href="/report/%s">File Bug</a> > ' % report
348*67e74705SXin Li        title = self.get_scalar_field('title')
349*67e74705SXin Li        description = self.get_scalar_field('description')
350*67e74705SXin Li
351*67e74705SXin Li        res,message = self.submit_bug(c)
352*67e74705SXin Li
353*67e74705SXin Li        if res:
354*67e74705SXin Li            statusClass = 'SubmitOk'
355*67e74705SXin Li            statusName = 'Succeeded'
356*67e74705SXin Li        else:
357*67e74705SXin Li            statusClass = 'SubmitFail'
358*67e74705SXin Li            statusName = 'Failed'
359*67e74705SXin Li
360*67e74705SXin Li        result = """
361*67e74705SXin Li<head>
362*67e74705SXin Li  <title>Bug Submission</title>
363*67e74705SXin Li  <link rel="stylesheet" type="text/css" href="/scanview.css" />
364*67e74705SXin Li</head>
365*67e74705SXin Li<body>
366*67e74705SXin Li<h3>
367*67e74705SXin Li<a href="/">Summary</a> >
368*67e74705SXin Li%(reportingFor)s
369*67e74705SXin Li%(fileBug)s
370*67e74705SXin LiSubmit</h3>
371*67e74705SXin Li<form name="form" action="">
372*67e74705SXin Li<table class="form">
373*67e74705SXin Li<tr><td>
374*67e74705SXin Li<table class="form_group">
375*67e74705SXin Li<tr>
376*67e74705SXin Li  <td class="form_clabel">Title:</td>
377*67e74705SXin Li  <td class="form_value">
378*67e74705SXin Li    <input type="text" name="title" size="50" value="%(title)s" disabled>
379*67e74705SXin Li  </td>
380*67e74705SXin Li</tr>
381*67e74705SXin Li<tr>
382*67e74705SXin Li  <td class="form_label">Description:</td>
383*67e74705SXin Li  <td class="form_value">
384*67e74705SXin Li<textarea rows="10" cols="80" name="description" disabled>
385*67e74705SXin Li%(description)s
386*67e74705SXin Li</textarea>
387*67e74705SXin Li  </td>
388*67e74705SXin Li</table>
389*67e74705SXin Li</td></tr>
390*67e74705SXin Li</table>
391*67e74705SXin Li</form>
392*67e74705SXin Li<h1 class="%(statusClass)s">Submission %(statusName)s</h1>
393*67e74705SXin Li%(message)s
394*67e74705SXin Li<p>
395*67e74705SXin Li<hr>
396*67e74705SXin Li<a href="/">Return to Summary</a>
397*67e74705SXin Li</body>
398*67e74705SXin Li</html>"""%locals()
399*67e74705SXin Li        return self.send_string(result)
400*67e74705SXin Li
401*67e74705SXin Li    def send_open_report(self, report):
402*67e74705SXin Li        try:
403*67e74705SXin Li            keys = self.load_report(report)
404*67e74705SXin Li        except IOError:
405*67e74705SXin Li            return self.send_error(400, 'Invalid report.')
406*67e74705SXin Li
407*67e74705SXin Li        file = keys.get('FILE')
408*67e74705SXin Li        if not file or not posixpath.exists(file):
409*67e74705SXin Li            return self.send_error(400, 'File does not exist: "%s"' % file)
410*67e74705SXin Li
411*67e74705SXin Li        import startfile
412*67e74705SXin Li        if self.server.options.debug:
413*67e74705SXin Li            print >>sys.stderr, '%s: SERVER: opening "%s"'%(sys.argv[0],
414*67e74705SXin Li                                                            file)
415*67e74705SXin Li
416*67e74705SXin Li        status = startfile.open(file)
417*67e74705SXin Li        if status:
418*67e74705SXin Li            res = 'Opened: "%s"' % file
419*67e74705SXin Li        else:
420*67e74705SXin Li            res = 'Open failed: "%s"' % file
421*67e74705SXin Li
422*67e74705SXin Li        return self.send_string(res, 'text/plain')
423*67e74705SXin Li
424*67e74705SXin Li    def get_report_context(self, report):
425*67e74705SXin Li        class Context:
426*67e74705SXin Li            pass
427*67e74705SXin Li        if report is None or report == 'None':
428*67e74705SXin Li            data = self.load_crashes()
429*67e74705SXin Li            # Don't allow empty reports.
430*67e74705SXin Li            if not data:
431*67e74705SXin Li                raise ValueError, 'No crashes detected!'
432*67e74705SXin Li            c = Context()
433*67e74705SXin Li            c.title = 'clang static analyzer failures'
434*67e74705SXin Li
435*67e74705SXin Li            stderrSummary = ""
436*67e74705SXin Li            for item in data:
437*67e74705SXin Li                if 'stderr' in item:
438*67e74705SXin Li                    path = posixpath.join(self.server.root, item['stderr'])
439*67e74705SXin Li                    if os.path.exists(path):
440*67e74705SXin Li                        lns = itertools.islice(open(path), 0, 10)
441*67e74705SXin Li                        stderrSummary += '%s\n--\n%s' % (item.get('src',
442*67e74705SXin Li                                                                  '<unknown>'),
443*67e74705SXin Li                                                         ''.join(lns))
444*67e74705SXin Li
445*67e74705SXin Li            c.description = """\
446*67e74705SXin LiThe clang static analyzer failed on these inputs:
447*67e74705SXin Li%s
448*67e74705SXin Li
449*67e74705SXin LiSTDERR Summary
450*67e74705SXin Li--------------
451*67e74705SXin Li%s
452*67e74705SXin Li""" % ('\n'.join([item.get('src','<unknown>') for item in data]),
453*67e74705SXin Li       stderrSummary)
454*67e74705SXin Li            c.reportSource = None
455*67e74705SXin Li            c.navMarkup = "Report Crashes > "
456*67e74705SXin Li            c.files = []
457*67e74705SXin Li            for item in data:
458*67e74705SXin Li                c.files.append(item.get('src',''))
459*67e74705SXin Li                c.files.append(posixpath.join(self.server.root,
460*67e74705SXin Li                                              item.get('file','')))
461*67e74705SXin Li                c.files.append(posixpath.join(self.server.root,
462*67e74705SXin Li                                              item.get('clangfile','')))
463*67e74705SXin Li                c.files.append(posixpath.join(self.server.root,
464*67e74705SXin Li                                              item.get('stderr','')))
465*67e74705SXin Li                c.files.append(posixpath.join(self.server.root,
466*67e74705SXin Li                                              item.get('info','')))
467*67e74705SXin Li            # Just in case something failed, ignore files which don't
468*67e74705SXin Li            # exist.
469*67e74705SXin Li            c.files = [f for f in c.files
470*67e74705SXin Li                       if os.path.exists(f) and os.path.isfile(f)]
471*67e74705SXin Li        else:
472*67e74705SXin Li            # Check that this is a valid report.
473*67e74705SXin Li            path = posixpath.join(self.server.root, 'report-%s.html' % report)
474*67e74705SXin Li            if not posixpath.exists(path):
475*67e74705SXin Li                raise ValueError, 'Invalid report ID'
476*67e74705SXin Li            keys = self.load_report(report)
477*67e74705SXin Li            c = Context()
478*67e74705SXin Li            c.title = keys.get('DESC','clang error (unrecognized')
479*67e74705SXin Li            c.description = """\
480*67e74705SXin LiBug reported by the clang static analyzer.
481*67e74705SXin Li
482*67e74705SXin LiDescription: %s
483*67e74705SXin LiFile: %s
484*67e74705SXin LiLine: %s
485*67e74705SXin Li"""%(c.title, keys.get('FILE','<unknown>'), keys.get('LINE', '<unknown>'))
486*67e74705SXin Li            c.reportSource = 'report-%s.html' % report
487*67e74705SXin Li            c.navMarkup = """<a href="/%s">Report %s</a> > """ % (c.reportSource,
488*67e74705SXin Li                                                                  report)
489*67e74705SXin Li
490*67e74705SXin Li            c.files = [path]
491*67e74705SXin Li        return c
492*67e74705SXin Li
493*67e74705SXin Li    def send_report(self, report, configOverrides=None):
494*67e74705SXin Li        def getConfigOption(section, field):
495*67e74705SXin Li            if (configOverrides is not None and
496*67e74705SXin Li                section in configOverrides and
497*67e74705SXin Li                field in configOverrides[section]):
498*67e74705SXin Li                return configOverrides[section][field]
499*67e74705SXin Li            return self.server.config.get(section, field)
500*67e74705SXin Li
501*67e74705SXin Li        # report is None is used for crashes
502*67e74705SXin Li        try:
503*67e74705SXin Li            c = self.get_report_context(report)
504*67e74705SXin Li        except ValueError, e:
505*67e74705SXin Li            return self.send_error(400, e.message)
506*67e74705SXin Li
507*67e74705SXin Li        title = c.title
508*67e74705SXin Li        description= c.description
509*67e74705SXin Li        reportingFor = c.navMarkup
510*67e74705SXin Li        if c.reportSource is None:
511*67e74705SXin Li            extraIFrame = ""
512*67e74705SXin Li        else:
513*67e74705SXin Li            extraIFrame = """\
514*67e74705SXin Li<iframe src="/%s" width="100%%" height="40%%"
515*67e74705SXin Li        scrolling="auto" frameborder="1">
516*67e74705SXin Li  <a href="/%s">View Bug Report</a>
517*67e74705SXin Li</iframe>""" % (c.reportSource, c.reportSource)
518*67e74705SXin Li
519*67e74705SXin Li        reporterSelections = []
520*67e74705SXin Li        reporterOptions = []
521*67e74705SXin Li
522*67e74705SXin Li        try:
523*67e74705SXin Li            active = int(getConfigOption('ScanView','reporter'))
524*67e74705SXin Li        except:
525*67e74705SXin Li            active = 0
526*67e74705SXin Li        for i,r in enumerate(self.server.reporters):
527*67e74705SXin Li            selected = (i == active)
528*67e74705SXin Li            if selected:
529*67e74705SXin Li                selectedStr = ' selected'
530*67e74705SXin Li            else:
531*67e74705SXin Li                selectedStr = ''
532*67e74705SXin Li            reporterSelections.append('<option value="%d"%s>%s</option>'%(i,selectedStr,r.getName()))
533*67e74705SXin Li            options = '\n'.join([ o.getHTML(r,title,getConfigOption) for o in r.getParameters()])
534*67e74705SXin Li            display = ('none','')[selected]
535*67e74705SXin Li            reporterOptions.append("""\
536*67e74705SXin Li<tr id="%sReporterOptions" style="display:%s">
537*67e74705SXin Li  <td class="form_label">%s Options</td>
538*67e74705SXin Li  <td class="form_value">
539*67e74705SXin Li    <table class="form_inner_group">
540*67e74705SXin Li%s
541*67e74705SXin Li    </table>
542*67e74705SXin Li  </td>
543*67e74705SXin Li</tr>
544*67e74705SXin Li"""%(r.getName(),display,r.getName(),options))
545*67e74705SXin Li        reporterSelections = '\n'.join(reporterSelections)
546*67e74705SXin Li        reporterOptionsDivs = '\n'.join(reporterOptions)
547*67e74705SXin Li        reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
548*67e74705SXin Li
549*67e74705SXin Li        if c.files:
550*67e74705SXin Li            fieldSize = min(5, len(c.files))
551*67e74705SXin Li            attachFileOptions = '\n'.join(["""\
552*67e74705SXin Li<option value="%d" selected>%s</option>""" % (i,v) for i,v in enumerate(c.files)])
553*67e74705SXin Li            attachFileRow = """\
554*67e74705SXin Li<tr>
555*67e74705SXin Li  <td class="form_label">Attach:</td>
556*67e74705SXin Li  <td class="form_value">
557*67e74705SXin Li<select style="width:100%%" name="files" multiple size=%d>
558*67e74705SXin Li%s
559*67e74705SXin Li</select>
560*67e74705SXin Li  </td>
561*67e74705SXin Li</tr>
562*67e74705SXin Li""" % (min(5, len(c.files)), attachFileOptions)
563*67e74705SXin Li        else:
564*67e74705SXin Li            attachFileRow = ""
565*67e74705SXin Li
566*67e74705SXin Li        result = """<html>
567*67e74705SXin Li<head>
568*67e74705SXin Li  <title>File Bug</title>
569*67e74705SXin Li  <link rel="stylesheet" type="text/css" href="/scanview.css" />
570*67e74705SXin Li</head>
571*67e74705SXin Li<script language="javascript" type="text/javascript">
572*67e74705SXin Livar reporters = %(reportersArray)s;
573*67e74705SXin Lifunction updateReporterOptions() {
574*67e74705SXin Li  index = document.getElementById('reporter').selectedIndex;
575*67e74705SXin Li  for (var i=0; i < reporters.length; ++i) {
576*67e74705SXin Li    o = document.getElementById(reporters[i] + "ReporterOptions");
577*67e74705SXin Li    if (i == index) {
578*67e74705SXin Li      o.style.display = "";
579*67e74705SXin Li    } else {
580*67e74705SXin Li      o.style.display = "none";
581*67e74705SXin Li    }
582*67e74705SXin Li  }
583*67e74705SXin Li}
584*67e74705SXin Li</script>
585*67e74705SXin Li<body onLoad="updateReporterOptions()">
586*67e74705SXin Li<h3>
587*67e74705SXin Li<a href="/">Summary</a> >
588*67e74705SXin Li%(reportingFor)s
589*67e74705SXin LiFile Bug</h3>
590*67e74705SXin Li<form name="form" action="/report_submit" method="post">
591*67e74705SXin Li<input type="hidden" name="report" value="%(report)s">
592*67e74705SXin Li
593*67e74705SXin Li<table class="form">
594*67e74705SXin Li<tr><td>
595*67e74705SXin Li<table class="form_group">
596*67e74705SXin Li<tr>
597*67e74705SXin Li  <td class="form_clabel">Title:</td>
598*67e74705SXin Li  <td class="form_value">
599*67e74705SXin Li    <input type="text" name="title" size="50" value="%(title)s">
600*67e74705SXin Li  </td>
601*67e74705SXin Li</tr>
602*67e74705SXin Li<tr>
603*67e74705SXin Li  <td class="form_label">Description:</td>
604*67e74705SXin Li  <td class="form_value">
605*67e74705SXin Li<textarea rows="10" cols="80" name="description">
606*67e74705SXin Li%(description)s
607*67e74705SXin Li</textarea>
608*67e74705SXin Li  </td>
609*67e74705SXin Li</tr>
610*67e74705SXin Li
611*67e74705SXin Li%(attachFileRow)s
612*67e74705SXin Li
613*67e74705SXin Li</table>
614*67e74705SXin Li<br>
615*67e74705SXin Li<table class="form_group">
616*67e74705SXin Li<tr>
617*67e74705SXin Li  <td class="form_clabel">Method:</td>
618*67e74705SXin Li  <td class="form_value">
619*67e74705SXin Li    <select id="reporter" name="reporter" onChange="updateReporterOptions()">
620*67e74705SXin Li    %(reporterSelections)s
621*67e74705SXin Li    </select>
622*67e74705SXin Li  </td>
623*67e74705SXin Li</tr>
624*67e74705SXin Li%(reporterOptionsDivs)s
625*67e74705SXin Li</table>
626*67e74705SXin Li<br>
627*67e74705SXin Li</td></tr>
628*67e74705SXin Li<tr><td class="form_submit">
629*67e74705SXin Li  <input align="right" type="submit" name="Submit" value="Submit">
630*67e74705SXin Li</td></tr>
631*67e74705SXin Li</table>
632*67e74705SXin Li</form>
633*67e74705SXin Li
634*67e74705SXin Li%(extraIFrame)s
635*67e74705SXin Li
636*67e74705SXin Li</body>
637*67e74705SXin Li</html>"""%locals()
638*67e74705SXin Li
639*67e74705SXin Li        return self.send_string(result)
640*67e74705SXin Li
641*67e74705SXin Li    def send_head(self, fields=None):
642*67e74705SXin Li        if (self.server.options.onlyServeLocal and
643*67e74705SXin Li            self.client_address[0] != '127.0.0.1'):
644*67e74705SXin Li            return self.send_error(401, 'Unauthorized host.')
645*67e74705SXin Li
646*67e74705SXin Li        if fields is None:
647*67e74705SXin Li            fields = {}
648*67e74705SXin Li        self.fields = fields
649*67e74705SXin Li
650*67e74705SXin Li        o = urlparse.urlparse(self.path)
651*67e74705SXin Li        self.fields = parse_query(o.query, fields)
652*67e74705SXin Li        path = posixpath.normpath(urllib.unquote(o.path))
653*67e74705SXin Li
654*67e74705SXin Li        # Split the components and strip the root prefix.
655*67e74705SXin Li        components = path.split('/')[1:]
656*67e74705SXin Li
657*67e74705SXin Li        # Special case some top-level entries.
658*67e74705SXin Li        if components:
659*67e74705SXin Li            name = components[0]
660*67e74705SXin Li            if len(components)==2:
661*67e74705SXin Li                if name=='report':
662*67e74705SXin Li                    return self.send_report(components[1])
663*67e74705SXin Li                elif name=='open':
664*67e74705SXin Li                    return self.send_open_report(components[1])
665*67e74705SXin Li            elif len(components)==1:
666*67e74705SXin Li                if name=='quit':
667*67e74705SXin Li                    self.server.halt()
668*67e74705SXin Li                    return self.send_string('Goodbye.', 'text/plain')
669*67e74705SXin Li                elif name=='report_submit':
670*67e74705SXin Li                    return self.send_report_submit()
671*67e74705SXin Li                elif name=='report_crashes':
672*67e74705SXin Li                    overrides = { 'ScanView' : {},
673*67e74705SXin Li                                  'Radar' : {},
674*67e74705SXin Li                                  'Email' : {} }
675*67e74705SXin Li                    for i,r in enumerate(self.server.reporters):
676*67e74705SXin Li                        if r.getName() == 'Radar':
677*67e74705SXin Li                            overrides['ScanView']['reporter'] = i
678*67e74705SXin Li                            break
679*67e74705SXin Li                    overrides['Radar']['Component'] = 'llvm - checker'
680*67e74705SXin Li                    overrides['Radar']['Component Version'] = 'X'
681*67e74705SXin Li                    return self.send_report(None, overrides)
682*67e74705SXin Li                elif name=='favicon.ico':
683*67e74705SXin Li                    return self.send_path(posixpath.join(kShare,'bugcatcher.ico'))
684*67e74705SXin Li
685*67e74705SXin Li        # Match directory entries.
686*67e74705SXin Li        if components[-1] == '':
687*67e74705SXin Li            components[-1] = 'index.html'
688*67e74705SXin Li
689*67e74705SXin Li        relpath = '/'.join(components)
690*67e74705SXin Li        path = posixpath.join(self.server.root, relpath)
691*67e74705SXin Li
692*67e74705SXin Li        if self.server.options.debug > 1:
693*67e74705SXin Li            print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
694*67e74705SXin Li                                                                 path)
695*67e74705SXin Li        return self.send_path(path)
696*67e74705SXin Li
697*67e74705SXin Li    def send_404(self):
698*67e74705SXin Li        self.send_error(404, "File not found")
699*67e74705SXin Li        return None
700*67e74705SXin Li
701*67e74705SXin Li    def send_path(self, path):
702*67e74705SXin Li        # If the requested path is outside the root directory, do not open it
703*67e74705SXin Li        rel = os.path.abspath(path)
704*67e74705SXin Li        if not rel.startswith(os.path.abspath(self.server.root)):
705*67e74705SXin Li          return self.send_404()
706*67e74705SXin Li
707*67e74705SXin Li        ctype = self.guess_type(path)
708*67e74705SXin Li        if ctype.startswith('text/'):
709*67e74705SXin Li            # Patch file instead
710*67e74705SXin Li            return self.send_patched_file(path, ctype)
711*67e74705SXin Li        else:
712*67e74705SXin Li            mode = 'rb'
713*67e74705SXin Li        try:
714*67e74705SXin Li            f = open(path, mode)
715*67e74705SXin Li        except IOError:
716*67e74705SXin Li            return self.send_404()
717*67e74705SXin Li        return self.send_file(f, ctype)
718*67e74705SXin Li
719*67e74705SXin Li    def send_file(self, f, ctype):
720*67e74705SXin Li        # Patch files to add links, but skip binary files.
721*67e74705SXin Li        self.send_response(200)
722*67e74705SXin Li        self.send_header("Content-type", ctype)
723*67e74705SXin Li        fs = os.fstat(f.fileno())
724*67e74705SXin Li        self.send_header("Content-Length", str(fs[6]))
725*67e74705SXin Li        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
726*67e74705SXin Li        self.end_headers()
727*67e74705SXin Li        return f
728*67e74705SXin Li
729*67e74705SXin Li    def send_string(self, s, ctype='text/html', headers=True, mtime=None):
730*67e74705SXin Li        if headers:
731*67e74705SXin Li            self.send_response(200)
732*67e74705SXin Li            self.send_header("Content-type", ctype)
733*67e74705SXin Li            self.send_header("Content-Length", str(len(s)))
734*67e74705SXin Li            if mtime is None:
735*67e74705SXin Li                mtime = self.dynamic_mtime
736*67e74705SXin Li            self.send_header("Last-Modified", self.date_time_string(mtime))
737*67e74705SXin Li            self.end_headers()
738*67e74705SXin Li        return StringIO.StringIO(s)
739*67e74705SXin Li
740*67e74705SXin Li    def send_patched_file(self, path, ctype):
741*67e74705SXin Li        # Allow a very limited set of variables. This is pretty gross.
742*67e74705SXin Li        variables = {}
743*67e74705SXin Li        variables['report'] = ''
744*67e74705SXin Li        m = kReportFileRE.match(path)
745*67e74705SXin Li        if m:
746*67e74705SXin Li            variables['report'] = m.group(2)
747*67e74705SXin Li
748*67e74705SXin Li        try:
749*67e74705SXin Li            f = open(path,'r')
750*67e74705SXin Li        except IOError:
751*67e74705SXin Li            return self.send_404()
752*67e74705SXin Li        fs = os.fstat(f.fileno())
753*67e74705SXin Li        data = f.read()
754*67e74705SXin Li        for a,b in kReportReplacements:
755*67e74705SXin Li            data = a.sub(b % variables, data)
756*67e74705SXin Li        return self.send_string(data, ctype, mtime=fs.st_mtime)
757*67e74705SXin Li
758*67e74705SXin Li
759*67e74705SXin Lidef create_server(address, options, root):
760*67e74705SXin Li    import Reporter
761*67e74705SXin Li
762*67e74705SXin Li    reporters = Reporter.getReporters()
763*67e74705SXin Li
764*67e74705SXin Li    return ScanViewServer(address, ScanViewRequestHandler,
765*67e74705SXin Li                          root,
766*67e74705SXin Li                          reporters,
767*67e74705SXin Li                          options)
768