diff options
author | Zancanaro; Carlo <czan8762@plang3.cs.usyd.edu.au> | 2012-09-24 09:58:17 +1000 |
---|---|---|
committer | Zancanaro; Carlo <czan8762@plang3.cs.usyd.edu.au> | 2012-09-24 09:58:17 +1000 |
commit | 222e2a7620e6520ffaf4fc4e69d79c18da31542e (patch) | |
tree | 7bfbc05bfa3b41c8f9d2e56d53a0bc3e310df239 /clang/tools/scan-view/ScanView.py | |
parent | 3d206f03985b50beacae843d880bccdc91a9f424 (diff) |
Add the clang library to the repo (with some of my changes, too).
Diffstat (limited to 'clang/tools/scan-view/ScanView.py')
-rw-r--r-- | clang/tools/scan-view/ScanView.py | 770 |
1 files changed, 770 insertions, 0 deletions
diff --git a/clang/tools/scan-view/ScanView.py b/clang/tools/scan-view/ScanView.py new file mode 100644 index 0000000..c6dddba --- /dev/null +++ b/clang/tools/scan-view/ScanView.py @@ -0,0 +1,770 @@ +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 +import itertools + +import Reporter +import ConfigParser + +### +# Various patterns matched or replaced by server. + +kReportFileRE = re.compile('(.*/)?report-(.*)\\.html') + +kBugKeyValueRE = re.compile('<!-- BUG([^ ]*) (.*) -->') + +# <!-- REPORTPROBLEM file="crashes/clang_crash_ndSGF9.mi" stderr="crashes/clang_crash_ndSGF9.mi.stderr.txt" info="crashes/clang_crash_ndSGF9.mi.info" --> + +kReportCrashEntryRE = re.compile('<!-- REPORTPROBLEM (.*?)-->') +kReportCrashEntryKeyValueRE = re.compile(' ?([^=]+)="(.*?)"') + +kReportReplacements = [] + +# Add custom javascript. +kReportReplacements.append((re.compile('<!-- SUMMARYENDHEAD -->'), """\ +<script language="javascript" type="text/javascript"> +function load(url) { + if (window.XMLHttpRequest) { + req = new XMLHttpRequest(); + } else if (window.ActiveXObject) { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } + if (req != undefined) { + req.open("GET", url, true); + req.send(""); + } +} +</script>""")) + +# Insert additional columns. +kReportReplacements.append((re.compile('<!-- REPORTBUGCOL -->'), + '<td></td><td></td>')) + +# Insert report bug and open file links. +kReportReplacements.append((re.compile('<!-- REPORTBUG id="report-(.*)\\.html" -->'), + ('<td class="Button"><a href="report/\\1">Report Bug</a></td>' + + '<td class="Button"><a href="javascript:load(\'open/\\1\')">Open File</a></td>'))) + +kReportReplacements.append((re.compile('<!-- REPORTHEADER -->'), + '<h3><a href="/">Summary</a> > Report %(report)s</h3>')) + +kReportReplacements.append((re.compile('<!-- REPORTSUMMARYEXTRA -->'), + '<td class="Button"><a href="report/%(report)s">Report Bug</a></td>')) + +# Insert report crashes link. + +# Disabled for the time being until we decide exactly when this should +# be enabled. Also the radar reporter needs to be fixed to report +# multiple files. + +#kReportReplacements.append((re.compile('<!-- REPORTCRASHES -->'), +# '<br>These files will automatically be attached to ' + +# 'reports filed here: <a href="report_crashes">Report Crashes</a>.')) + +### +# Other simple parameters + +kResources = posixpath.join(posixpath.dirname(__file__), 'Resources') +kConfigPath = os.path.expanduser('~/.scanview.cfg') + +### + +__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.success = False + self.status = None + + def run(self): + result = None + try: + if self.server.options.debug: + print >>sys.stderr, "%s: SERVER: submitting bug."%(sys.argv[0],) + self.status = self.reporter.fileReport(self.report, self.parameters) + self.success = True + time.sleep(3) + if self.server.options.debug: + print >>sys.stderr, "%s: SERVER: submission complete."%(sys.argv[0],) + except Reporter.ReportFailure,e: + self.status = e.value + except Exception,e: + s = StringIO.StringIO() + import traceback + print >>s,'<b>Unhandled Exception</b><br><pre>' + traceback.print_exc(e,file=s) + print >>s,'</pre>' + 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 + self.config = None + self.load_config() + + def load_config(self): + self.config = ConfigParser.RawConfigParser() + + # Add defaults + self.config.add_section('ScanView') + for r in self.reporters: + self.config.add_section(r.getName()) + for p in r.getParameters(): + if p.saveConfigValue(): + self.config.set(r.getName(), p.getName(), '') + + # Ignore parse errors + try: + self.config.read([kConfigPath]) + except: + pass + + # Save on exit + import atexit + atexit.register(lambda: self.save_config()) + + def save_config(self): + # Ignore errors (only called on exit). + try: + f = open(kConfigPath,'w') + self.config.write(f) + f.close() + except: + pass + + 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 finish_request(self, request, client_address): + if self.options.autoReload: + import ScanView + self.RequestHandlerClass = reload(ScanView).ScanViewRequestHandler + BaseHTTPServer.HTTPServer.finish_request(self, request, client_address) + + 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(self, 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('+', ' ')) + item = fields.get(name) + if item is None: + fields[name] = [value] + else: + item.append(value) + return fields + +class ScanViewRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + server_version = "ScanViewServer/" + __version__ + dynamic_mtime = time.time() + + 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 load_crashes(self): + path = posixpath.join(self.server.root, 'index.html') + data = open(path).read() + problems = [] + for item in kReportCrashEntryRE.finditer(data): + fieldData = item.group(1) + fields = dict([i.groups() for i in + kReportCrashEntryKeyValueRE.finditer(fieldData)]) + problems.append(fields) + return problems + + 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 get_scalar_field(self, name): + if name in self.fields: + return self.fields[name][0] + else: + return None + + def submit_bug(self, c): + title = self.get_scalar_field('title') + description = self.get_scalar_field('description') + report = self.get_scalar_field('report') + reporterIndex = self.get_scalar_field('reporter') + files = [] + for fileID in self.fields.get('files',[]): + try: + i = int(fileID) + except: + i = None + if i is None or i<0 or i>=len(c.files): + return (False, 'Invalid file ID') + files.append(c.files[i]) + + if not title: + return (False, "Missing title.") + if not description: + return (False, "Missing description.") + try: + reporterIndex = int(reporterIndex) + except: + return (False, "Invalid report method.") + + # Get the reporter and parameters. + reporter = self.server.reporters[reporterIndex] + parameters = {} + for o in reporter.getParameters(): + name = '%s_%s'%(reporter.getName(),o.getName()) + if name not in self.fields: + return (False, + 'Missing field "%s" for %s report method.'%(name, + reporter.getName())) + parameters[o.getName()] = self.get_scalar_field(name) + + # Update config defaults. + if report != 'None': + self.server.config.set('ScanView', 'reporter', reporterIndex) + for o in reporter.getParameters(): + if o.saveConfigValue(): + name = o.getName() + self.server.config.set(reporter.getName(), name, parameters[name]) + + # Create the report. + bug = Reporter.BugReport(title, description, files) + + # Kick off a reporting thread. + t = ReporterThread(bug, reporter, parameters, self.server) + t.start() + + # Wait for thread to die... + while t.isAlive(): + time.sleep(.25) + submitStatus = t.status + + return (t.success, t.status) + + def send_report_submit(self): + report = self.get_scalar_field('report') + c = self.get_report_context(report) + if c.reportSource is None: + reportingFor = "Report Crashes > " + fileBug = """\ +<a href="/report_crashes">File Bug</a> > """%locals() + else: + reportingFor = '<a href="/%s">Report %s</a> > ' % (c.reportSource, + report) + fileBug = '<a href="/report/%s">File Bug</a> > ' % report + title = self.get_scalar_field('title') + description = self.get_scalar_field('description') + + res,message = self.submit_bug(c) + + if res: + statusClass = 'SubmitOk' + statusName = 'Succeeded' + else: + statusClass = 'SubmitFail' + statusName = 'Failed' + + result = """ +<head> + <title>Bug Submission</title> + <link rel="stylesheet" type="text/css" href="/scanview.css" /> +</head> +<body> +<h3> +<a href="/">Summary</a> > +%(reportingFor)s +%(fileBug)s +Submit</h3> +<form name="form" action=""> +<table class="form"> +<tr><td> +<table class="form_group"> +<tr> + <td class="form_clabel">Title:</td> + <td class="form_value"> + <input type="text" name="title" size="50" value="%(title)s" disabled> + </td> +</tr> +<tr> + <td class="form_label">Description:</td> + <td class="form_value"> +<textarea rows="10" cols="80" name="description" disabled> +%(description)s +</textarea> + </td> +</table> +</td></tr> +</table> +</form> +<h1 class="%(statusClass)s">Submission %(statusName)s</h1> +%(message)s +<p> +<hr> +<a href="/">Return to Summary</a> +</body> +</html>"""%locals() + return self.send_string(result) + + def send_open_report(self, report): + try: + keys = self.load_report(report) + except IOError: + return self.send_error(400, 'Invalid report.') + + file = keys.get('FILE') + if not file or not posixpath.exists(file): + return self.send_error(400, 'File does not exist: "%s"' % file) + + import startfile + if self.server.options.debug: + print >>sys.stderr, '%s: SERVER: opening "%s"'%(sys.argv[0], + file) + + status = startfile.open(file) + if status: + res = 'Opened: "%s"' % file + else: + res = 'Open failed: "%s"' % file + + return self.send_string(res, 'text/plain') + + def get_report_context(self, report): + class Context: + pass + if report is None or report == 'None': + data = self.load_crashes() + # Don't allow empty reports. + if not data: + raise ValueError, 'No crashes detected!' + c = Context() + c.title = 'clang static analyzer failures' + + stderrSummary = "" + for item in data: + if 'stderr' in item: + path = posixpath.join(self.server.root, item['stderr']) + if os.path.exists(path): + lns = itertools.islice(open(path), 0, 10) + stderrSummary += '%s\n--\n%s' % (item.get('src', + '<unknown>'), + ''.join(lns)) + + c.description = """\ +The clang static analyzer failed on these inputs: +%s + +STDERR Summary +-------------- +%s +""" % ('\n'.join([item.get('src','<unknown>') for item in data]), + stderrSummary) + c.reportSource = None + c.navMarkup = "Report Crashes > " + c.files = [] + for item in data: + c.files.append(item.get('src','')) + c.files.append(posixpath.join(self.server.root, + item.get('file',''))) + c.files.append(posixpath.join(self.server.root, + item.get('clangfile',''))) + c.files.append(posixpath.join(self.server.root, + item.get('stderr',''))) + c.files.append(posixpath.join(self.server.root, + item.get('info',''))) + # Just in case something failed, ignore files which don't + # exist. + c.files = [f for f in c.files + if os.path.exists(f) and os.path.isfile(f)] + else: + # Check that this is a valid report. + path = posixpath.join(self.server.root, 'report-%s.html' % report) + if not posixpath.exists(path): + raise ValueError, 'Invalid report ID' + keys = self.load_report(report) + c = Context() + c.title = keys.get('DESC','clang error (unrecognized') + c.description = """\ +Bug reported by the clang static analyzer. + +Description: %s +File: %s +Line: %s +"""%(c.title, keys.get('FILE','<unknown>'), keys.get('LINE', '<unknown>')) + c.reportSource = 'report-%s.html' % report + c.navMarkup = """<a href="/%s">Report %s</a> > """ % (c.reportSource, + report) + + c.files = [path] + return c + + def send_report(self, report, configOverrides=None): + def getConfigOption(section, field): + if (configOverrides is not None and + section in configOverrides and + field in configOverrides[section]): + return configOverrides[section][field] + return self.server.config.get(section, field) + + # report is None is used for crashes + try: + c = self.get_report_context(report) + except ValueError, e: + return self.send_error(400, e.message) + + title = c.title + description= c.description + reportingFor = c.navMarkup + if c.reportSource is None: + extraIFrame = "" + else: + extraIFrame = """\ +<iframe src="/%s" width="100%%" height="40%%" + scrolling="auto" frameborder="1"> + <a href="/%s">View Bug Report</a> +</iframe>""" % (c.reportSource, c.reportSource) + + reporterSelections = [] + reporterOptions = [] + + try: + active = int(getConfigOption('ScanView','reporter')) + except: + active = 0 + for i,r in enumerate(self.server.reporters): + selected = (i == active) + if selected: + selectedStr = ' selected' + else: + selectedStr = '' + reporterSelections.append('<option value="%d"%s>%s</option>'%(i,selectedStr,r.getName())) + options = '\n'.join([ o.getHTML(r,title,getConfigOption) for o in r.getParameters()]) + display = ('none','')[selected] + reporterOptions.append("""\ +<tr id="%sReporterOptions" style="display:%s"> + <td class="form_label">%s Options</td> + <td class="form_value"> + <table class="form_inner_group"> +%s + </table> + </td> +</tr> +"""%(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])) + + if c.files: + fieldSize = min(5, len(c.files)) + attachFileOptions = '\n'.join(["""\ +<option value="%d" selected>%s</option>""" % (i,v) for i,v in enumerate(c.files)]) + attachFileRow = """\ +<tr> + <td class="form_label">Attach:</td> + <td class="form_value"> +<select style="width:100%%" name="files" multiple size=%d> +%s +</select> + </td> +</tr> +""" % (min(5, len(c.files)), attachFileOptions) + else: + attachFileRow = "" + + result = """<html> +<head> + <title>File Bug</title> + <link rel="stylesheet" type="text/css" href="/scanview.css" /> +</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 = ""; + } else { + o.style.display = "none"; + } + } +} +</script> +<body onLoad="updateReporterOptions()"> +<h3> +<a href="/">Summary</a> > +%(reportingFor)s +File Bug</h3> +<form name="form" action="/report_submit" method="post"> +<input type="hidden" name="report" value="%(report)s"> + +<table class="form"> +<tr><td> +<table class="form_group"> +<tr> + <td class="form_clabel">Title:</td> + <td class="form_value"> + <input type="text" name="title" size="50" value="%(title)s"> + </td> +</tr> +<tr> + <td class="form_label">Description:</td> + <td class="form_value"> +<textarea rows="10" cols="80" name="description"> +%(description)s +</textarea> + </td> +</tr> + +%(attachFileRow)s + +</table> +<br> +<table class="form_group"> +<tr> + <td class="form_clabel">Method:</td> + <td class="form_value"> + <select id="reporter" name="reporter" onChange="updateReporterOptions()"> + %(reporterSelections)s + </select> + </td> +</tr> +%(reporterOptionsDivs)s +</table> +<br> +</td></tr> +<tr><td class="form_submit"> + <input align="right" type="submit" name="Submit" value="Submit"> +</td></tr> +</table> +</form> + +%(extraIFrame)s + +</body> +</html>"""%locals() + + return self.send_string(result) + + def send_head(self, fields=None): + if (self.server.options.onlyServeLocal and + self.client_address[0] != '127.0.0.1'): + return self.send_error(401, 'Unauthorized host.') + + 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 len(components)==2: + if name=='report': + return self.send_report(components[1]) + elif name=='open': + return self.send_open_report(components[1]) + elif len(components)==1: + if name=='quit': + self.server.halt() + return self.send_string('Goodbye.', 'text/plain') + elif name=='report_submit': + return self.send_report_submit() + elif name=='report_crashes': + overrides = { 'ScanView' : {}, + 'Radar' : {}, + 'Email' : {} } + for i,r in enumerate(self.server.reporters): + if r.getName() == 'Radar': + overrides['ScanView']['reporter'] = i + break + overrides['Radar']['Component'] = 'llvm - checker' + overrides['Radar']['Component Version'] = 'X' + return self.send_report(None, overrides) + elif name=='favicon.ico': + return self.send_path(posixpath.join(kResources,'bugcatcher.ico')) + + # Match directory entries. + if components[-1] == '': + components[-1] = 'index.html' + + suffix = '/'.join(components) + + # The summary may reference source files on disk using rooted + # paths. Make sure these resolve correctly for now. + # FIXME: This isn't a very good idea... we should probably + # mark rooted paths somehow. + if os.path.exists(posixpath.join('/', suffix)): + path = posixpath.join('/', suffix) + else: + path = posixpath.join(self.server.root, suffix) + + 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 is None: + mtime = self.dynamic_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): + # Allow a very limited set of variables. This is pretty gross. + variables = {} + variables['report'] = '' + m = kReportFileRE.match(path) + if m: + variables['report'] = m.group(2) + + try: + f = open(path,'r') + except IOError: + return self.send_404() + fs = os.fstat(f.fileno()) + data = f.read() + for a,b in kReportReplacements: + data = a.sub(b % variables, data) + return self.send_string(data, ctype, mtime=fs.st_mtime) + + +def create_server(address, options, root): + import Reporter + + reporters = Reporter.getReporters() + + return ScanViewServer(address, ScanViewRequestHandler, + root, + reporters, + options) |