[cfe-commits] r56375 - in /cfe/trunk/tools: ./ scan-view/ scan-view/Reporter.py scan-view/Resources/ scan-view/Resources/FileRadar.scpt scan-view/Resources/GetRadarVersion.scpt scan-view/ScanView.py scan-view/scan-view
Daniel Dunbar
daniel at zuster.org
Fri Sep 19 16:32:11 PDT 2008
Author: ddunbar
Date: Fri Sep 19 18:32:11 2008
New Revision: 56375
URL: http://llvm.org/viewvc/llvm-project?rev=56375&view=rev
Log:
Add initial implementation of scan-view
- Web based interface to static analyzer.
Added:
cfe/trunk/tools/
cfe/trunk/tools/scan-view/
cfe/trunk/tools/scan-view/Reporter.py
cfe/trunk/tools/scan-view/Resources/
cfe/trunk/tools/scan-view/Resources/FileRadar.scpt (with props)
cfe/trunk/tools/scan-view/Resources/GetRadarVersion.scpt
cfe/trunk/tools/scan-view/ScanView.py
cfe/trunk/tools/scan-view/scan-view (with props)
Added: cfe/trunk/tools/scan-view/Reporter.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/scan-view/Reporter.py?rev=56375&view=auto
==============================================================================
--- cfe/trunk/tools/scan-view/Reporter.py (added)
+++ cfe/trunk/tools/scan-view/Reporter.py Fri Sep 19 18:32:11 2008
@@ -0,0 +1,159 @@
+"""Methods for reporting bugs."""
+
+import subprocess, sys, os
+
+__all__ = ['BugReport', 'getReporters']
+
+# Collect information about a bug.
+
+class BugReport:
+ def __init__(self, title, description, files):
+ self.title = title
+ self.description = description
+ self.files = files
+
+# Reporter interfaces.
+
+import os
+
+import email, mimetypes, smtplib
+from email import encoders
+from email.message import Message
+from email.mime.base import MIMEBase
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+
+class EmailReporter:
+ def getName(self):
+ return 'Email'
+
+ def getParameterNames(self):
+ return ['To', 'From', 'SMTP Server', 'SMTP Port']
+
+ # Lifted from python email module examples.
+ def attachFile(self, outer, path):
+ # Guess the content type based on the file's extension. Encoding
+ # will be ignored, although we should check for simple things like
+ # gzip'd or compressed files.
+ ctype, encoding = mimetypes.guess_type(path)
+ if ctype is None or encoding is not None:
+ # No guess could be made, or the file is encoded (compressed), so
+ # use a generic bag-of-bits type.
+ ctype = 'application/octet-stream'
+ maintype, subtype = ctype.split('/', 1)
+ if maintype == 'text':
+ fp = open(path)
+ # Note: we should handle calculating the charset
+ msg = MIMEText(fp.read(), _subtype=subtype)
+ fp.close()
+ else:
+ fp = open(path, 'rb')
+ msg = MIMEBase(maintype, subtype)
+ msg.set_payload(fp.read())
+ fp.close()
+ # Encode the payload using Base64
+ encoders.encode_base64(msg)
+ # Set the filename parameter
+ msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(path))
+ outer.attach(msg)
+
+ def fileReport(self, report, parameters):
+ mainMsg = """\
+BUG REPORT
+---
+Title: %s
+Description: %s
+"""%(report.title, report.description)
+
+ if not parameters.get('To') or not parameters.get('From'):
+ raise ValueError,'Invalid email parameters.'
+
+ msg = MIMEMultipart()
+ msg['Subject'] = 'BUG REPORT: %s'%(report.title)
+ # FIXME: Get config parameters
+ msg['To'] = parameters.get('To')
+ msg['From'] = parameters.get('From')
+ msg.preamble = mainMsg
+
+ msg.attach(MIMEText(mainMsg, _subtype='text/plain'))
+ for file in report.files:
+ self.attachFile(msg, file)
+
+ s = smtplib.SMTP(host=parameters.get('SMTP Server'),
+ port=parameters.get('SMTP Port'))
+ s.sendmail(msg['From'], msg['To'], msg.as_string())
+ s.close()
+
+class BugzillaReporter:
+ def getName(self):
+ return 'Bugzilla'
+
+ def getParameterNames(self):
+ return ['URL', 'Product']
+
+ def fileReport(self, report, parameters):
+ raise NotImplementedError
+
+class RadarReporter:
+ @staticmethod
+ def isAvailable():
+ # FIXME: Find this .scpt better
+ path = os.path.join(os.path.dirname(__file__),'Resources/GetRadarVersion.scpt')
+ try:
+ p = subprocess.Popen(['osascript',path],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except:
+ return False
+ data,err = p.communicate()
+ res = p.wait()
+ # FIXME: Check version? Check for no errors?
+ return res == 0
+
+ def getName(self):
+ return 'Radar'
+
+ def getParameterNames(self):
+ return ['Component', 'Component Version']
+
+ def fileReport(self, report, parameters):
+ component = parameters.get('Component', '')
+ componentVersion = parameters.get('Component Version', '')
+ personID = ""
+ diagnosis = ""
+ config = ""
+
+ if not component.strip():
+ component = 'Bugs found by clang Analyzer'
+ if not componentVersion.strip():
+ componentVersion = 'X'
+
+ script = os.path.join(os.path.dirname(__file__),'Resources/FileRadar.scpt')
+ args = ['osascript', script, component, componentVersion, personID, report.title,
+ report.description, diagnosis, config] + map(os.path.abspath, report.files)
+# print >>sys.stderr, args
+ try:
+ p = subprocess.Popen(args,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except:
+ print >>sys.stderr, '%s: SERVER: radar failed'%(sys.argv[0],)
+ sys.print_exc()
+ raise
+ data, err = p.communicate()
+# print >>sys.stderr, '%s: SERVER: radar report: "%s" "%s"'%(sys.argv[0],data, err)
+ res = p.wait()
+# print >>sys.stderr, '%s: SERVER: radar report res: %d'%(sys.argv[0],res,)
+
+ if res:
+ raise RuntimeError,'Radar submission failed.'
+
+ return data.replace('\n','\n<br>')
+
+###
+
+def getReporters():
+ reporters = []
+ if RadarReporter.isAvailable():
+ reporters.append(RadarReporter())
+ reporters.extend([EmailReporter(), BugzillaReporter()])
+ return reporters
+
Added: cfe/trunk/tools/scan-view/Resources/FileRadar.scpt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/scan-view/Resources/FileRadar.scpt?rev=56375&view=auto
==============================================================================
Binary file - no diff available.
Propchange: cfe/trunk/tools/scan-view/Resources/FileRadar.scpt
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: cfe/trunk/tools/scan-view/Resources/GetRadarVersion.scpt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/scan-view/Resources/GetRadarVersion.scpt?rev=56375&view=auto
==============================================================================
(empty)
Added: cfe/trunk/tools/scan-view/ScanView.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/scan-view/ScanView.py?rev=56375&view=auto
==============================================================================
--- cfe/trunk/tools/scan-view/ScanView.py (added)
+++ cfe/trunk/tools/scan-view/ScanView.py Fri Sep 19 18:32:11 2008
@@ -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)
Added: cfe/trunk/tools/scan-view/scan-view
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/scan-view/scan-view?rev=56375&view=auto
==============================================================================
--- cfe/trunk/tools/scan-view/scan-view (added)
+++ cfe/trunk/tools/scan-view/scan-view Fri Sep 19 18:32:11 2008
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+
+"""The clang static analyzer results viewer.
+"""
+
+import sys
+import thread
+import time
+import urllib
+import webbrowser
+
+# How long to wait for server to start.
+kSleepTimeout = .05
+kMaxSleeps = 100
+
+# Default server parameters
+
+kDefaultHost = 'localhost'
+kDefaultPort = 8181
+
+###
+
+def url_is_up(url):
+ try:
+ o = urllib.urlopen(url)
+ except IOError:
+ return False
+ o.close()
+ return True
+
+def start_browser(options):
+ import urllib, webbrowser
+
+ url = 'http://%s:%d'%(options.host, options.port)
+
+ # Wait for server to start...
+ if options.debug:
+ sys.stderr.write('%s: Waiting for server.' % sys.argv[0])
+ sys.stderr.flush()
+ for i in range(kMaxSleeps):
+ if url_is_up(url):
+ break
+ if options.debug:
+ sys.stderr.write('.')
+ sys.stderr.flush()
+ time.sleep(kSleepTimeout)
+ else:
+ print >>sys.stderr,'WARNING: Unable to detect that server started.'
+
+ if options.debug:
+ print >>sys.stderr,'%s: Starting webbrowser...' % sys.argv[0]
+ webbrowser.open(url)
+
+def run(options, root):
+ import ScanView
+ try:
+ if options.debug:
+ print >>sys.stderr,'%s: SERVER: starting %s:%d'%(sys.argv[0],
+ options.host,
+ options.port)
+ httpd = ScanView.create_server(options, root)
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ pass
+
+def main():
+ from optparse import OptionParser
+ parser = OptionParser('usage: %prog [options] <results directory>')
+ parser.set_description(__doc__)
+ parser.add_option(
+ '--host', dest="host", default=kDefaultHost, type="string",
+ help="Host interface to listen on. (default=%s)" % kDefaultHost)
+ parser.add_option(
+ '--port', dest="port", default=kDefaultPort, type="int",
+ help="Port to listen on. (default=%s)" % kDefaultPort)
+ parser.add_option("--debug", dest="debug", default=0,
+ action="count",
+ help="Print additional debugging information.")
+ parser.add_option("--no-browser", dest="startBrowser", default=True,
+ action="store_false",
+ help="Don't open a webbrowser on startup.")
+ (options, args) = parser.parse_args()
+
+ if len(args) != 1:
+ parser.error('invalid number of arguments.')
+ root, = args
+
+ # Kick off thread to wait for server and start web browser, if
+ # requested.
+ if options.startBrowser:
+ t = thread.start_new_thread(start_browser, (options,))
+
+ run(options, root)
+
+if __name__ == '__main__':
+ main()
Propchange: cfe/trunk/tools/scan-view/scan-view
------------------------------------------------------------------------------
svn:executable = *
More information about the cfe-commits
mailing list