[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