summaryrefslogtreecommitdiff
path: root/clang/tools/scan-view
diff options
context:
space:
mode:
Diffstat (limited to 'clang/tools/scan-view')
-rw-r--r--clang/tools/scan-view/Reporter.py248
-rw-r--r--clang/tools/scan-view/Resources/FileRadar.scptbin0 -> 18418 bytes
-rw-r--r--clang/tools/scan-view/Resources/GetRadarVersion.scpt0
-rw-r--r--clang/tools/scan-view/Resources/bugcatcher.icobin0 -> 318 bytes
-rw-r--r--clang/tools/scan-view/ScanView.py770
-rwxr-xr-xclang/tools/scan-view/scan-view131
-rw-r--r--clang/tools/scan-view/startfile.py203
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
new file mode 100644
index 0000000..1c74552
--- /dev/null
+++ b/clang/tools/scan-view/Resources/FileRadar.scpt
Binary files differ
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
new file mode 100644
index 0000000..22d39b5
--- /dev/null
+++ b/clang/tools/scan-view/Resources/bugcatcher.ico
Binary files differ
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)