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