Add initial implementation of scan-view
- Web based interface to static analyzer.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@56375 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/tools/scan-view/ScanView.py b/tools/scan-view/ScanView.py
new file mode 100644
index 0000000..e340a9f
--- /dev/null
+++ b/tools/scan-view/ScanView.py
@@ -0,0 +1,388 @@
+import BaseHTTPServer
+import SimpleHTTPServer
+import os
+import sys
+import urllib, urlparse
+import posixpath
+import StringIO
+import re
+import shutil
+import threading
+import time
+import socket
+
+from Reporter import BugReport
+
+# Keys replaced by server.
+
+kReportBugRE = re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->')
+kReportBugRepl = '<a class="ReportBugLink" href="report/\\1">Report Bug</a>'
+kBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->')
+
+###
+
+__version__ = "0.1"
+
+__all__ = ["create_server"]
+
+class ReporterThread(threading.Thread):
+ def __init__(self, report, reporter, parameters, server):
+ threading.Thread.__init__(self)
+ self.report = report
+ self.server = server
+ self.reporter = reporter
+ self.parameters = parameters
+ self.status = None
+
+ def run(self):
+ result = None
+ try:
+ if self.server.options.debug:
+ print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],)
+ result = self.reporter.fileReport(self.report, self.parameters)
+ time.sleep(3)
+ if self.server.options.debug:
+ print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],)
+ except Exception,e:
+ s = StringIO.StringIO()
+ import traceback
+ print >>s,'Submission Failed<br><pre>'
+ traceback.print_exc(e,file=s)
+ print >>s,'</pre>'
+ self.status = s.getvalue()
+ return
+
+ s = StringIO.StringIO()
+ print >>s, 'Submission Complete!'
+ print >>s, '<hr>'
+ if result is not None:
+ print >>s, result
+ self.status = s.getvalue()
+
+class ScanViewServer(BaseHTTPServer.HTTPServer):
+ def __init__(self, address, handler, root, reporters, options):
+ BaseHTTPServer.HTTPServer.__init__(self, address, handler)
+ self.root = root
+ self.reporters = reporters
+ self.options = options
+ self.halted = False
+
+ def halt(self):
+ self.halted = True
+ if self.options.debug:
+ print >>sys.stderr, "%s: SERVER: halting." % (sys.argv[0],)
+
+ def serve_forever(self):
+ while not self.halted:
+ if self.options.debug > 1:
+ print >>sys.stderr, "%s: SERVER: waiting..." % (sys.argv[0],)
+ try:
+ self.handle_request()
+ except OSError,e:
+ print 'OSError',e.errno
+
+ def handle_error(self, request, client_address):
+ # Ignore socket errors
+ info = sys.exc_info()
+ if info and isinstance(info[1], socket.error):
+ if self.options.debug > 1:
+ print >>sys.stderr, "%s: SERVER: ignored socket error." % (sys.argv[0],)
+ return
+ BaseHTTPServer.HTTPServer.handle_error(request, client_address)
+
+# Borrowed from Quixote, with simplifications.
+def parse_query(qs, fields=None):
+ if fields is None:
+ fields = {}
+ for chunk in filter(None, qs.split('&')):
+ if '=' not in chunk:
+ name = chunk
+ value = ''
+ else:
+ name, value = chunk.split('=', 1)
+ name = urllib.unquote(name.replace('+', ' '))
+ value = urllib.unquote(value.replace('+', ' '))
+ fields[name] = value
+ return fields
+
+class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ server_version = "ScanViewServer/" + __version__
+
+ def do_HEAD(self):
+ try:
+ SimpleHTTPServer.SimpleHTTPRequestHandler.do_HEAD(self)
+ except Exception,e:
+ self.handle_exception(e)
+
+ def do_GET(self):
+ try:
+ SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+ except Exception,e:
+ self.handle_exception(e)
+
+ def do_POST(self):
+ """Serve a POST request."""
+ try:
+ length = self.headers.getheader('content-length') or "0"
+ try:
+ length = int(length)
+ except:
+ length = 0
+ content = self.rfile.read(length)
+ fields = parse_query(content)
+ f = self.send_head(fields)
+ if f:
+ self.copyfile(f, self.wfile)
+ f.close()
+ except Exception,e:
+ self.handle_exception(e)
+
+ def log_message(self, format, *args):
+ if self.server.options.debug:
+ sys.stderr.write("%s: SERVER: %s - - [%s] %s\n" %
+ (sys.argv[0],
+ self.address_string(),
+ self.log_date_time_string(),
+ format%args))
+
+ def load_report(self, report):
+ path = os.path.join(self.server.root, 'report-%s.html'%report)
+ data = open(path).read()
+ keys = {}
+ for item in kBugKeyValueRE.finditer(data):
+ k,v = item.groups()
+ keys[k] = v
+ return keys
+
+ def handle_exception(self, exc):
+ import traceback
+ s = StringIO.StringIO()
+ print >>s, "INTERNAL ERROR\n"
+ traceback.print_exc(exc, s)
+ f = self.send_string(s.getvalue(), 'text/plain')
+ if f:
+ self.copyfile(f, self.wfile)
+ f.close()
+
+ def send_internal_error(self, message):
+ return self.send_string('ERROR: %s'%(message,), 'text/plain')
+
+ def send_report_submit(self):
+ s = StringIO.StringIO()
+ report = self.fields.get('report')
+ reporter = self.fields.get('reporter')
+ title = self.fields.get('title')
+ description = self.fields.get('description')
+
+ # Get the reporter and parameters.
+ reporter = self.server.reporters[int(reporter)]
+ parameters = {}
+ for o in reporter.getParameterNames():
+ name = '%s_%s'%(reporter.getName(),o)
+ parameters[o] = self.fields.get(name)
+
+ # Create the report.
+ path = os.path.join(self.server.root, 'report-%s.html'%report)
+ files = [path]
+ br = BugReport(title, description, files)
+
+ # Send back an initial response and wait for the report to
+ # finish.
+ initial_response = """<html>
+<head><title>Filing Report</title></head>
+<body>
+<h1>Filing Report</h1>
+<b>Report</b>: %(report)s<br>
+<b>Title</b>: %(title)s<br>
+<b>Description</b>: %(description)s<br>
+<hr>
+Submission in progress."""%locals()
+
+ self.send_response(200)
+ self.send_header("Content-type", 'text/html')
+ self.end_headers()
+ self.wfile.write(initial_response)
+ self.wfile.flush()
+
+ # Kick off a reporting thread.
+ t = ReporterThread(br, reporter, parameters, self.server)
+ t.start()
+
+ # Wait for thread to die...
+ while t.isAlive():
+ self.wfile.write('.')
+ self.wfile.flush()
+ time.sleep(.25)
+ submitStatus = t.status
+
+ end_response = """<br>
+%(submitStatus)s
+<hr>
+<a href="/">Home</a>
+</body>
+</html>
+"""%locals()
+ return self.send_string(end_response, headers=False)
+
+ def send_report(self, report):
+ try:
+ keys = self.load_report(report)
+ except IOError:
+ return self.send_internal_error('Invalid report.')
+
+ initialTitle = keys.get('DESC','')
+ initialDescription = 'Bug generated by the clang static analyzer.'
+
+ keysAndValues = '\n'.join(['<b>%s</b>: %s<br>'%(k,v) for k,v in keys.items()])
+ reporterSelections = []
+ reporterOptions = []
+
+ for i,r in enumerate(self.server.reporters):
+ reporterSelections.append('<option value="%d">%s</option>'%(i,r.getName()))
+ options = '\n'.join(['%s: <input type="text" name="%s_%s"><br>'%(o,r.getName(),o) for o in r.getParameterNames()])
+ if i==0:
+ display = 'inline'
+ else:
+ display = 'none'
+ reporterOptions.append('<div id="%sReporterOptions" style="display:%s">\n<h3>%s Options</h3>%s\n</div>'%(r.getName(),display,r.getName(),options))
+ reporterSelections = '\n'.join(reporterSelections)
+ reporterOptionsDivs = '\n'.join(reporterOptions)
+ reportersArray = '[%s]'%(','.join([`r.getName()` for r in self.server.reporters]))
+
+ result = """<html>
+<head>
+ <title>File Report</title>
+</head>
+<script language="javascript" type="text/javascript">
+var reporters = %(reportersArray)s;
+function updateReporterOptions() {
+ index = document.getElementById('reporter').selectedIndex;
+ for (var i=0; i < reporters.length; ++i) {
+ o = document.getElementById(reporters[i] + "ReporterOptions");
+ if (i == index) {
+ o.style.display = "inline";
+ } else {
+ o.style.display = "none";
+ }
+ }
+}
+</script>
+<body>
+<h1>File Report</h1>
+%(keysAndValues)s
+<hr>
+<form name="form" action="/report_submit" method="post">
+Title:
+<input type="text" name="title" size="50" value="%(initialTitle)s"><br>
+Description:<br>
+<textarea rows="10" cols="80" name="description">
+%(initialDescription)s
+</textarea><br>
+<hr>
+<input type="hidden" name="report" value="%(report)s">
+Method: <select id="reporter" name="reporter" onChange="updateReporterOptions()">
+%(reporterSelections)s
+</select><br>
+<hr>
+%(reporterOptionsDivs)s
+<hr>
+<input type="submit" name="Submit" value="Submit">
+</form>
+</body>
+</html>"""%locals()
+ return self.send_string(result)
+
+ def send_head(self, fields=None):
+ if fields is None:
+ fields = {}
+ self.fields = fields
+
+ o = urlparse.urlparse(self.path)
+ self.fields = parse_query(o.query, fields)
+ path = posixpath.normpath(urllib.unquote(o.path))
+
+ # Split the components and strip the root prefix.
+ components = path.split('/')[1:]
+
+ # Special case some top-level entries.
+ if components:
+ name = components[0]
+ if name=='quit':
+ self.server.halt()
+ return self.send_string('Goodbye.', 'text/plain')
+ elif name=='report':
+ if len(components)==2:
+ return self.send_report(components[1])
+ else:
+ return self.send_404()
+ elif name=='report_submit':
+ if len(components)==1:
+ return self.send_report_submit()
+ else:
+ return self.send_404()
+
+ # Match directory entries.
+ if components[-1] == '':
+ components[-1] = 'index.html'
+
+ path = posixpath.join(self.server.root, '/'.join(components))
+ if self.server.options.debug > 1:
+ print >>sys.stderr, '%s: SERVER: sending path "%s"'%(sys.argv[0],
+ path)
+ return self.send_path(path)
+
+ def send_404(self):
+ self.send_error(404, "File not found")
+ return None
+
+ def send_path(self, path):
+ ctype = self.guess_type(path)
+ if ctype.startswith('text/'):
+ # Patch file instead
+ return self.send_patched_file(path, ctype)
+ else:
+ mode = 'rb'
+ try:
+ f = open(path, mode)
+ except IOError:
+ return self.send_404()
+ return self.send_file(f, ctype)
+
+ def send_file(self, f, ctype):
+ # Patch files to add links, but skip binary files.
+ self.send_response(200)
+ self.send_header("Content-type", ctype)
+ fs = os.fstat(f.fileno())
+ self.send_header("Content-Length", str(fs[6]))
+ self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
+ self.end_headers()
+ return f
+
+ def send_string(self, s, ctype='text/html', headers=True, mtime=None):
+ if headers:
+ self.send_response(200)
+ self.send_header("Content-type", ctype)
+ self.send_header("Content-Length", str(len(s)))
+ if mtime:
+ self.send_header("Last-Modified", self.date_time_string(mtime))
+ self.end_headers()
+ return StringIO.StringIO(s)
+
+ def send_patched_file(self, path, ctype):
+ f = open(path,'r')
+ fs = os.fstat(f.fileno())
+ data = f.read()
+ data = kReportBugRE.sub(kReportBugRepl, data)
+ return self.send_string(data, ctype, mtime=fs.st_mtime)
+
+
+def create_server(options, root):
+ import Reporter
+
+ reporters = Reporter.getReporters()
+
+ return ScanViewServer((options.host, options.port),
+ ScanViewRequestHandler,
+ root,
+ reporters,
+ options)