From d4c2337ef55f146e8ed0122a9407257e56b39717 Mon Sep 17 00:00:00 2001 From: Daniel Dunbar Date: Fri, 19 Sep 2008 23:32:11 +0000 Subject: [PATCH] Add initial implementation of scan-view - Web based interface to static analyzer. llvm-svn: 56375 --- clang/tools/scan-view/Reporter.py | 159 +++++++ .../tools/scan-view/Resources/FileRadar.scpt | Bin 0 -> 15290 bytes .../scan-view/Resources/GetRadarVersion.scpt | 0 clang/tools/scan-view/ScanView.py | 388 ++++++++++++++++++ clang/tools/scan-view/scan-view | 96 +++++ 5 files changed, 643 insertions(+) create mode 100644 clang/tools/scan-view/Reporter.py create mode 100644 clang/tools/scan-view/Resources/FileRadar.scpt create mode 100644 clang/tools/scan-view/Resources/GetRadarVersion.scpt create mode 100644 clang/tools/scan-view/ScanView.py create mode 100755 clang/tools/scan-view/scan-view diff --git a/clang/tools/scan-view/Reporter.py b/clang/tools/scan-view/Reporter.py new file mode 100644 index 000000000000..a28d0945ac3e --- /dev/null +++ b/clang/tools/scan-view/Reporter.py @@ -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
') + +### + +def getReporters(): + reporters = [] + if RadarReporter.isAvailable(): + reporters.append(RadarReporter()) + reporters.extend([EmailReporter(), BugzillaReporter()]) + return reporters + diff --git a/clang/tools/scan-view/Resources/FileRadar.scpt b/clang/tools/scan-view/Resources/FileRadar.scpt new file mode 100644 index 0000000000000000000000000000000000000000..145386c30c6a12c125c8186be44d2cfacbdb2b7a GIT binary patch literal 15290 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�Nk9J|akLopv54U3y#`~aNYY2EN^#%Qo!ruGd zd)~X=JKo#gTi%=A8{X^QYu>BgE8ffAOU8S@fx3Ecr*!r14t4cTgLL(F19bIPy}EjH zySjR#UR@dQ^_>hA81J>64)qxC)g49*jQ2`r)id79>7%>xUi#aCE{DITUcAD4(R%^c z9Ns~SekHQen~<3OjT>=eIBl)q1a;(mF2s3v01xMp-t#<(W8QOI>Mi%4^`7yTc~5&! zc~5#zc#nIJc}u+|d?Vk?^Y~VNl%MbxdyjgL@O%7$x5#_gdno9=NLfKp%MN+Z*Dcv0 z@431~Ipi%zN#m6v50@zXuwux2MxP=U81k0+r9@@Odm1H;rG>nwP|^@r$a@kcjYoyN zC$t}eMj`KUEg{Mi@*YD;!!JQ^DYXoFOX`lu^@UMG-ePoC|1IP_ijw+EA@7kbhw8t3 zfFW-Y`sr!7vG=e$|AstVy!9isv68=^!Xw@a9>1amk6T%^7>nqVQKdyoM}1lJ*r?LB zbn}AMxpQ;B%0+F#n%sH0YyHx?+*@+L_Dk!1_)YGuxf}e_x4H9kH~OVbxwqxUD90~j zmCU^zwH9@?U2^Y4tz})URqkD=?W(oisOz*lVzgVgqG@G&QnLjyYMpxz9`2rt);9O# zqqqn9O&B#^R*k25U1GH7{1~;pEk^C;#b~d&2;JNjqxQGNsKc!>>UevM_P#Slxp&8? zQ#0yZ^u#D&+$VP-(Cq8OE+}<%gb(}=!rd|?yK4{7%5zM_D1YI~W>lbDyr&oofu%=X zpZtX}>KUW`&|&{k3!0B>9+c*~s(+=v)Q=JV5lQr&3OK=;!|$W;??C)3bP=TgXsZhe zA);PS7z86y{wQ-ps4!9hSe1<3WLwx z@1*H{GnFlh8!P_5Ft69Hn') +kReportBugRepl = 'Report Bug' +kBugKeyValueRE = re.compile('') + +### + +__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

'
+            traceback.print_exc(e,file=s)
+            print >>s,'
' + self.status = s.getvalue() + return + + s = StringIO.StringIO() + print >>s, 'Submission Complete!' + print >>s, '
' + 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 = """ +Filing Report + +

Filing Report

+Report: %(report)s
+Title: %(title)s
+Description: %(description)s
+
+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 = """
+%(submitStatus)s +
+Home + + +"""%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(['%s: %s
'%(k,v) for k,v in keys.items()]) + reporterSelections = [] + reporterOptions = [] + + for i,r in enumerate(self.server.reporters): + reporterSelections.append(''%(i,r.getName())) + options = '\n'.join(['%s:
'%(o,r.getName(),o) for o in r.getParameterNames()]) + if i==0: + display = 'inline' + else: + display = 'none' + reporterOptions.append('
\n

%s Options

%s\n
'%(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 = """ + + File Report + + + +

File Report

+%(keysAndValues)s +
+
+Title: +
+Description:
+
+
+ +Method:
+
+%(reporterOptionsDivs)s +
+ +
+ +"""%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) diff --git a/clang/tools/scan-view/scan-view b/clang/tools/scan-view/scan-view new file mode 100755 index 000000000000..db3d3bb87c7e --- /dev/null +++ b/clang/tools/scan-view/scan-view @@ -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] ') + 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()