diff options
author | Carlo Zancanaro <carlo@pc-4w14-0.cs.usyd.edu.au> | 2012-10-15 17:10:06 +1100 |
---|---|---|
committer | Carlo Zancanaro <carlo@pc-4w14-0.cs.usyd.edu.au> | 2012-10-15 17:10:06 +1100 |
commit | be1de4be954c80875ad4108e0a33e8e131b2f2c0 (patch) | |
tree | 1fbbecf276bf7c7bdcbb4dd446099d6d90eaa516 /clang/tools/scan-view | |
parent | c4626a62754862d20b41e8a46a3574264ea80e6d (diff) | |
parent | f1bd2e48c5324d3f7cda4090c87f8a5b6f463ce2 (diff) |
Merge branch 'master' of ssh://bitbucket.org/czan/honours
Diffstat (limited to 'clang/tools/scan-view')
-rw-r--r-- | clang/tools/scan-view/Reporter.py | 248 | ||||
-rw-r--r-- | clang/tools/scan-view/Resources/FileRadar.scpt | bin | 0 -> 18418 bytes | |||
-rw-r--r-- | clang/tools/scan-view/Resources/GetRadarVersion.scpt | 0 | ||||
-rw-r--r-- | clang/tools/scan-view/Resources/bugcatcher.ico | bin | 0 -> 318 bytes | |||
-rw-r--r-- | clang/tools/scan-view/ScanView.py | 770 | ||||
-rwxr-xr-x | clang/tools/scan-view/scan-view | 131 | ||||
-rw-r--r-- | clang/tools/scan-view/startfile.py | 203 |
7 files changed, 1352 insertions, 0 deletions
diff --git a/clang/tools/scan-view/Reporter.py b/clang/tools/scan-view/Reporter.py new file mode 100644 index 0000000..9560fc1 --- /dev/null +++ b/clang/tools/scan-view/Reporter.py @@ -0,0 +1,248 @@ +"""Methods for reporting bugs.""" + +import subprocess, sys, os + +__all__ = ['ReportFailure', 'BugReport', 'getReporters'] + +# + +class ReportFailure(Exception): + """Generic exception for failures in bug reporting.""" + def __init__(self, value): + self.value = value + +# 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 + +#===------------------------------------------------------------------------===# +# ReporterParameter +#===------------------------------------------------------------------------===# + +class ReporterParameter: + def __init__(self, n): + self.name = n + def getName(self): + return self.name + def getValue(self,r,bugtype,getConfigOption): + return getConfigOption(r.getName(),self.getName()) + def saveConfigValue(self): + return True + +class TextParameter (ReporterParameter): + def getHTML(self,r,bugtype,getConfigOption): + return """\ +<tr> +<td class="form_clabel">%s:</td> +<td class="form_value"><input type="text" name="%s_%s" value="%s"></td> +</tr>"""%(self.getName(),r.getName(),self.getName(),self.getValue(r,bugtype,getConfigOption)) + +class SelectionParameter (ReporterParameter): + def __init__(self, n, values): + ReporterParameter.__init__(self,n) + self.values = values + + def getHTML(self,r,bugtype,getConfigOption): + default = self.getValue(r,bugtype,getConfigOption) + return """\ +<tr> +<td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s"> +%s +</select></td>"""%(self.getName(),r.getName(),self.getName(),'\n'.join(["""\ +<option value="%s"%s>%s</option>"""%(o[0], + o[0] == default and ' selected="selected"' or '', + o[1]) for o in self.values])) + +#===------------------------------------------------------------------------===# +# Reporters +#===------------------------------------------------------------------------===# + +class EmailReporter: + def getName(self): + return 'Email' + + def getParameters(self): + return map(lambda x:TextParameter(x),['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'): + raise ReportFailure('No "To" address specified.') + if not parameters.get('From'): + raise ReportFailure('No "From" address specified.') + + 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) + + try: + s = smtplib.SMTP(host=parameters.get('SMTP Server'), + port=parameters.get('SMTP Port')) + s.sendmail(msg['From'], msg['To'], msg.as_string()) + s.close() + except: + raise ReportFailure('Unable to send message via SMTP.') + + return "Message sent!" + +class BugzillaReporter: + def getName(self): + return 'Bugzilla' + + def getParameters(self): + return map(lambda x:TextParameter(x),['URL','Product']) + + def fileReport(self, report, parameters): + raise NotImplementedError + + +class RadarClassificationParameter(SelectionParameter): + def __init__(self): + SelectionParameter.__init__(self,"Classification", + [['1', 'Security'], ['2', 'Crash/Hang/Data Loss'], + ['3', 'Performance'], ['4', 'UI/Usability'], + ['6', 'Serious Bug'], ['7', 'Other']]) + + def saveConfigValue(self): + return False + + def getValue(self,r,bugtype,getConfigOption): + if bugtype.find("leak") != -1: + return '3' + elif bugtype.find("dereference") != -1: + return '2' + elif bugtype.find("missing ivar release") != -1: + return '3' + else: + return '7' + +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 getParameters(self): + return [ TextParameter('Component'), TextParameter('Component Version'), + RadarClassificationParameter() ] + + def fileReport(self, report, parameters): + component = parameters.get('Component', '') + componentVersion = parameters.get('Component Version', '') + classification = parameters.get('Classification', '') + 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, classification, 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: + raise ReportFailure("Unable to file radar (AppleScript failure).") + data, err = p.communicate() + res = p.wait() + + if res: + raise ReportFailure("Unable to file radar (AppleScript failure).") + + try: + values = eval(data) + except: + raise ReportFailure("Unable to process radar results.") + + # We expect (int: bugID, str: message) + if len(values) != 2 or not isinstance(values[0], int): + raise ReportFailure("Unable to process radar results.") + + bugID,message = values + bugID = int(bugID) + + if not bugID: + raise ReportFailure(message) + + return "Filed: <a href=\"rdar://%d/\">%d</a>"%(bugID,bugID) + +### + +def getReporters(): + reporters = [] + if RadarReporter.isAvailable(): + reporters.append(RadarReporter()) + reporters.append(EmailReporter()) + return reporters + diff --git a/clang/tools/scan-view/Resources/FileRadar.scpt b/clang/tools/scan-view/Resources/FileRadar.scpt Binary files differnew file mode 100644 index 0000000..1c74552 --- /dev/null +++ b/clang/tools/scan-view/Resources/FileRadar.scpt diff --git a/clang/tools/scan-view/Resources/GetRadarVersion.scpt b/clang/tools/scan-view/Resources/GetRadarVersion.scpt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/clang/tools/scan-view/Resources/GetRadarVersion.scpt diff --git a/clang/tools/scan-view/Resources/bugcatcher.ico b/clang/tools/scan-view/Resources/bugcatcher.ico Binary files differnew file mode 100644 index 0000000..22d39b5 --- /dev/null +++ b/clang/tools/scan-view/Resources/bugcatcher.ico 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) diff --git a/clang/tools/scan-view/scan-view b/clang/tools/scan-view/scan-view new file mode 100755 index 0000000..fb27da6 --- /dev/null +++ b/clang/tools/scan-view/scan-view @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +"""The clang static analyzer results viewer. +""" + +import sys +import posixpath +import thread +import time +import urllib +import webbrowser + +# How long to wait for server to start. +kSleepTimeout = .05 +kMaxSleeps = int(60 / kSleepTimeout) + +# Default server parameters + +kDefaultHost = '127.0.0.1' +kDefaultPort = 8181 +kMaxPortsToTry = 100 + +### + +def url_is_up(url): + try: + o = urllib.urlopen(url) + except IOError: + return False + o.close() + return True + +def start_browser(port, options): + import urllib, webbrowser + + url = 'http://%s:%d'%(options.host, 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(port, options, root): + import ScanView + try: + print 'Starting scan-view at: http://%s:%d'%(options.host, + port) + print ' Use Ctrl-C to exit.' + httpd = ScanView.create_server((options.host, port), + options, root) + httpd.serve_forever() + except KeyboardInterrupt: + pass + +def port_is_open(port): + import SocketServer + try: + t = SocketServer.TCPServer((kDefaultHost,port),None) + except: + return False + t.server_close() + return True + +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=None, 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("--auto-reload", dest="autoReload", default=False, + action="store_true", + help="Automatically update module for each request.") + parser.add_option("--no-browser", dest="startBrowser", default=True, + action="store_false", + help="Don't open a webbrowser on startup.") + parser.add_option("--allow-all-hosts", dest="onlyServeLocal", default=True, + action="store_false", + help='Allow connections from any host (access restricted to "127.0.0.1" by default)') + (options, args) = parser.parse_args() + + if len(args) != 1: + parser.error('No results directory specified.') + root, = args + + # Make sure this directory is in a reasonable state to view. + if not posixpath.exists(posixpath.join(root,'index.html')): + parser.error('Invalid directory, analysis results not found!') + + # Find an open port. We aren't particularly worried about race + # conditions here. Note that if the user specified a port we only + # use that one. + if options.port is not None: + port = options.port + else: + for i in range(kMaxPortsToTry): + if port_is_open(kDefaultPort + i): + port = kDefaultPort + i + break + else: + parser.error('Unable to find usable port in [%d,%d)'%(kDefaultPort, + kDefaultPort+kMaxPortsToTry)) + + # Kick off thread to wait for server and start web browser, if + # requested. + if options.startBrowser: + t = thread.start_new_thread(start_browser, (port,options)) + + run(port, options, root) + +if __name__ == '__main__': + main() diff --git a/clang/tools/scan-view/startfile.py b/clang/tools/scan-view/startfile.py new file mode 100644 index 0000000..e8fbe2d --- /dev/null +++ b/clang/tools/scan-view/startfile.py @@ -0,0 +1,203 @@ +"""Utility for opening a file using the default application in a cross-platform +manner. Modified from http://code.activestate.com/recipes/511443/. +""" + +__version__ = '1.1x' +__all__ = ['open'] + +import os +import sys +import webbrowser +import subprocess + +_controllers = {} +_open = None + + +class BaseController(object): + '''Base class for open program controllers.''' + + def __init__(self, name): + self.name = name + + def open(self, filename): + raise NotImplementedError + + +class Controller(BaseController): + '''Controller for a generic open program.''' + + def __init__(self, *args): + super(Controller, self).__init__(os.path.basename(args[0])) + self.args = list(args) + + def _invoke(self, cmdline): + if sys.platform[:3] == 'win': + closefds = False + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + else: + closefds = True + startupinfo = None + + if (os.environ.get('DISPLAY') or sys.platform[:3] == 'win' or + sys.platform == 'darwin'): + inout = file(os.devnull, 'r+') + else: + # for TTY programs, we need stdin/out + inout = None + + # if possible, put the child precess in separate process group, + # so keyboard interrupts don't affect child precess as well as + # Python + setsid = getattr(os, 'setsid', None) + if not setsid: + setsid = getattr(os, 'setpgrp', None) + + pipe = subprocess.Popen(cmdline, stdin=inout, stdout=inout, + stderr=inout, close_fds=closefds, + preexec_fn=setsid, startupinfo=startupinfo) + + # It is assumed that this kind of tools (gnome-open, kfmclient, + # exo-open, xdg-open and open for OSX) immediately exit after lauching + # the specific application + returncode = pipe.wait() + if hasattr(self, 'fixreturncode'): + returncode = self.fixreturncode(returncode) + return not returncode + + def open(self, filename): + if isinstance(filename, basestring): + cmdline = self.args + [filename] + else: + # assume it is a sequence + cmdline = self.args + filename + try: + return self._invoke(cmdline) + except OSError: + return False + + +# Platform support for Windows +if sys.platform[:3] == 'win': + + class Start(BaseController): + '''Controller for the win32 start progam through os.startfile.''' + + def open(self, filename): + try: + os.startfile(filename) + except WindowsError: + # [Error 22] No application is associated with the specified + # file for this operation: '<URL>' + return False + else: + return True + + _controllers['windows-default'] = Start('start') + _open = _controllers['windows-default'].open + + +# Platform support for MacOS +elif sys.platform == 'darwin': + _controllers['open']= Controller('open') + _open = _controllers['open'].open + + +# Platform support for Unix +else: + + import commands + + # @WARNING: use the private API of the webbrowser module + from webbrowser import _iscommand + + class KfmClient(Controller): + '''Controller for the KDE kfmclient program.''' + + def __init__(self, kfmclient='kfmclient'): + super(KfmClient, self).__init__(kfmclient, 'exec') + self.kde_version = self.detect_kde_version() + + def detect_kde_version(self): + kde_version = None + try: + info = commands.getoutput('kde-config --version') + + for line in info.splitlines(): + if line.startswith('KDE'): + kde_version = line.split(':')[-1].strip() + break + except (OSError, RuntimeError): + pass + + return kde_version + + def fixreturncode(self, returncode): + if returncode is not None and self.kde_version > '3.5.4': + return returncode + else: + return os.EX_OK + + def detect_desktop_environment(): + '''Checks for known desktop environments + + Return the desktop environments name, lowercase (kde, gnome, xfce) + or "generic" + + ''' + + desktop_environment = 'generic' + + if os.environ.get('KDE_FULL_SESSION') == 'true': + desktop_environment = 'kde' + elif os.environ.get('GNOME_DESKTOP_SESSION_ID'): + desktop_environment = 'gnome' + else: + try: + info = commands.getoutput('xprop -root _DT_SAVE_MODE') + if ' = "xfce4"' in info: + desktop_environment = 'xfce' + except (OSError, RuntimeError): + pass + + return desktop_environment + + + def register_X_controllers(): + if _iscommand('kfmclient'): + _controllers['kde-open'] = KfmClient() + + for command in ('gnome-open', 'exo-open', 'xdg-open'): + if _iscommand(command): + _controllers[command] = Controller(command) + + def get(): + controllers_map = { + 'gnome': 'gnome-open', + 'kde': 'kde-open', + 'xfce': 'exo-open', + } + + desktop_environment = detect_desktop_environment() + + try: + controller_name = controllers_map[desktop_environment] + return _controllers[controller_name].open + + except KeyError: + if _controllers.has_key('xdg-open'): + return _controllers['xdg-open'].open + else: + return webbrowser.open + + + if os.environ.get("DISPLAY"): + register_X_controllers() + _open = get() + + +def open(filename): + '''Open a file or an URL in the registered default application.''' + + return _open(filename) |