[rake] Add analyze task which performs a static analysis on the codebase.

Vendored relavant part from http://clang-analyzer.llvm.org/downloads/checker-275.tar.bz2
This commit is contained in:
Eloy Durán
2013-11-12 16:03:38 +01:00
parent 5f44d912af
commit e5f12d9a2c
16 changed files with 4404 additions and 0 deletions

View File

@@ -56,6 +56,13 @@ end
desc "Build all targets"
task :build => targets.map { |x| "build:#{x}" }
desc "Run the clang static analyzer against the source"
task :analyze do
sh './static-analyzer/scan-build --keep-empty --use-analyzer=/usr/bin/clang -o ./static-analysis rake'
results = Dir.glob('static-analysis/*').sort_by { |path| File.mtime(path) }.last
sh "./static-analyzer/scan-view #{results}"
end
targets.each do |target|
desc "Clean target #{target}"
task "clean:#{target}" do

248
static-analyzer/Reporter.py Normal file
View File

@@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

767
static-analyzer/ScanView.py Normal file
View File

@@ -0,0 +1,767 @@
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'
relpath = '/'.join(components)
path = posixpath.join(self.server.root, relpath)
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):
# If the requested path is outside the root directory, do not open it
rel = os.path.abspath(path)
if not rel.startswith(os.path.abspath(self.server.root)):
return self.send_404()
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)

Binary file not shown.

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env perl
use Cwd qw/ abs_path /;
use File::Basename qw/ dirname /;
# Add scan-build dir to the list of places where perl looks for modules.
use lib dirname(abs_path($0));
do 'ccc-analyzer';

View File

@@ -0,0 +1,710 @@
#!/usr/bin/env perl
#
# The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
#
##===----------------------------------------------------------------------===##
#
# A script designed to interpose between the build system and gcc. It invokes
# both gcc and the static analyzer.
#
##===----------------------------------------------------------------------===##
use strict;
use warnings;
use FindBin;
use Cwd qw/ getcwd abs_path /;
use File::Temp qw/ tempfile /;
use File::Path qw / mkpath /;
use File::Basename;
use Text::ParseWords;
##===----------------------------------------------------------------------===##
# Compiler command setup.
##===----------------------------------------------------------------------===##
my $Compiler;
my $Clang;
my $DefaultCCompiler;
my $DefaultCXXCompiler;
if (`uname -a` =~ m/Darwin/) {
$DefaultCCompiler = 'clang';
$DefaultCXXCompiler = 'clang++';
} else {
$DefaultCCompiler = 'gcc';
$DefaultCXXCompiler = 'g++';
}
if ($FindBin::Script =~ /c\+\+-analyzer/) {
$Compiler = $ENV{'CCC_CXX'};
if (!defined $Compiler) { $Compiler = $DefaultCXXCompiler; }
$Clang = $ENV{'CLANG_CXX'};
if (!defined $Clang) { $Clang = 'clang++'; }
}
else {
$Compiler = $ENV{'CCC_CC'};
if (!defined $Compiler) { $Compiler = $DefaultCCompiler; }
$Clang = $ENV{'CLANG'};
if (!defined $Clang) { $Clang = 'clang'; }
}
##===----------------------------------------------------------------------===##
# Cleanup.
##===----------------------------------------------------------------------===##
my $ReportFailures = $ENV{'CCC_REPORT_FAILURES'};
if (!defined $ReportFailures) { $ReportFailures = 1; }
my $CleanupFile;
my $ResultFile;
# Remove any stale files at exit.
END {
if (defined $ResultFile && -z $ResultFile) {
`rm -f $ResultFile`;
}
if (defined $CleanupFile) {
`rm -f $CleanupFile`;
}
}
##----------------------------------------------------------------------------##
# Process Clang Crashes.
##----------------------------------------------------------------------------##
sub GetPPExt {
my $Lang = shift;
if ($Lang =~ /objective-c\+\+/) { return ".mii" };
if ($Lang =~ /objective-c/) { return ".mi"; }
if ($Lang =~ /c\+\+/) { return ".ii"; }
return ".i";
}
# Set this to 1 if we want to include 'parser rejects' files.
my $IncludeParserRejects = 0;
my $ParserRejects = "Parser Rejects";
my $AttributeIgnored = "Attribute Ignored";
my $OtherError = "Other Error";
sub ProcessClangFailure {
my ($Clang, $Lang, $file, $Args, $HtmlDir, $ErrorType, $ofile) = @_;
my $Dir = "$HtmlDir/failures";
mkpath $Dir;
my $prefix = "clang_crash";
if ($ErrorType eq $ParserRejects) {
$prefix = "clang_parser_rejects";
}
elsif ($ErrorType eq $AttributeIgnored) {
$prefix = "clang_attribute_ignored";
}
elsif ($ErrorType eq $OtherError) {
$prefix = "clang_other_error";
}
# Generate the preprocessed file with Clang.
my ($PPH, $PPFile) = tempfile( $prefix . "_XXXXXX",
SUFFIX => GetPPExt($Lang),
DIR => $Dir);
system $Clang, @$Args, "-E", "-o", $PPFile;
close ($PPH);
# Create the info file.
open (OUT, ">", "$PPFile.info.txt") or die "Cannot open $PPFile.info.txt\n";
print OUT abs_path($file), "\n";
print OUT "$ErrorType\n";
print OUT "@$Args\n";
close OUT;
`uname -a >> $PPFile.info.txt 2>&1`;
`$Compiler -v >> $PPFile.info.txt 2>&1`;
system 'mv',$ofile,"$PPFile.stderr.txt";
return (basename $PPFile);
}
##----------------------------------------------------------------------------##
# Running the analyzer.
##----------------------------------------------------------------------------##
sub GetCCArgs {
my $mode = shift;
my $Args = shift;
pipe (FROM_CHILD, TO_PARENT);
my $pid = fork();
if ($pid == 0) {
close FROM_CHILD;
open(STDOUT,">&", \*TO_PARENT);
open(STDERR,">&", \*TO_PARENT);
exec $Clang, "-###", $mode, @$Args;
}
close(TO_PARENT);
my $line;
while (<FROM_CHILD>) {
next if (!/-cc1/);
$line = $_;
}
waitpid($pid,0);
close(FROM_CHILD);
die "could not find clang line\n" if (!defined $line);
# Strip the newline and initial whitspace
chomp $line;
$line =~ s/^\s+//;
my @items = quotewords('\s+', 0, $line);
my $cmd = shift @items;
die "cannot find 'clang' in 'clang' command\n" if (!($cmd =~ /clang/));
return \@items;
}
sub Analyze {
my ($Clang, $OriginalArgs, $AnalyzeArgs, $Lang, $Output, $Verbose, $HtmlDir,
$file) = @_;
my @Args = @$OriginalArgs;
my $Cmd;
my @CmdArgs;
my @CmdArgsSansAnalyses;
if ($Lang =~ /header/) {
exit 0 if (!defined ($Output));
$Cmd = 'cp';
push @CmdArgs, $file;
# Remove the PCH extension.
$Output =~ s/[.]gch$//;
push @CmdArgs, $Output;
@CmdArgsSansAnalyses = @CmdArgs;
}
else {
$Cmd = $Clang;
# Create arguments for doing regular parsing.
my $SyntaxArgs = GetCCArgs("-fsyntax-only", \@Args);
@CmdArgsSansAnalyses = @$SyntaxArgs;
# Create arguments for doing static analysis.
if (defined $ResultFile) {
push @Args, '-o', $ResultFile;
}
elsif (defined $HtmlDir) {
push @Args, '-o', $HtmlDir;
}
if ($Verbose) {
push @Args, "-Xclang", "-analyzer-display-progress";
}
foreach my $arg (@$AnalyzeArgs) {
push @Args, "-Xclang", $arg;
}
# Display Ubiviz graph?
if (defined $ENV{'CCC_UBI'}) {
push @Args, "-Xclang", "-analyzer-viz-egraph-ubigraph";
}
my $AnalysisArgs = GetCCArgs("--analyze", \@Args);
@CmdArgs = @$AnalysisArgs;
}
my @PrintArgs;
my $dir;
if ($Verbose) {
$dir = getcwd();
print STDERR "\n[LOCATION]: $dir\n";
push @PrintArgs,"'$Cmd'";
foreach my $arg (@CmdArgs) {
push @PrintArgs,"\'$arg\'";
}
}
if ($Verbose == 1) {
# We MUST print to stderr. Some clients use the stdout output of
# gcc for various purposes.
print STDERR join(' ', @PrintArgs);
print STDERR "\n";
}
elsif ($Verbose == 2) {
print STDERR "#SHELL (cd '$dir' && @PrintArgs)\n";
}
# Capture the STDERR of clang and send it to a temporary file.
# Capture the STDOUT of clang and reroute it to ccc-analyzer's STDERR.
# We save the output file in the 'crashes' directory if clang encounters
# any problems with the file.
pipe (FROM_CHILD, TO_PARENT);
my $pid = fork();
if ($pid == 0) {
close FROM_CHILD;
open(STDOUT,">&", \*TO_PARENT);
open(STDERR,">&", \*TO_PARENT);
exec $Cmd, @CmdArgs;
}
close TO_PARENT;
my ($ofh, $ofile) = tempfile("clang_output_XXXXXX", DIR => $HtmlDir);
while (<FROM_CHILD>) {
print $ofh $_;
print STDERR $_;
}
close $ofh;
waitpid($pid,0);
close(FROM_CHILD);
my $Result = $?;
# Did the command die because of a signal?
if ($ReportFailures) {
if ($Result & 127 and $Cmd eq $Clang and defined $HtmlDir) {
ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses,
$HtmlDir, "Crash", $ofile);
}
elsif ($Result) {
if ($IncludeParserRejects && !($file =~/conftest/)) {
ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses,
$HtmlDir, $ParserRejects, $ofile);
} else {
ProcessClangFailure($Clang, $Lang, $file, \@CmdArgsSansAnalyses,
$HtmlDir, $OtherError, $ofile);
}
}
else {
# Check if there were any unhandled attributes.
if (open(CHILD, $ofile)) {
my %attributes_not_handled;
# Don't flag warnings about the following attributes that we
# know are currently not supported by Clang.
$attributes_not_handled{"cdecl"} = 1;
my $ppfile;
while (<CHILD>) {
next if (! /warning: '([^\']+)' attribute ignored/);
# Have we already spotted this unhandled attribute?
next if (defined $attributes_not_handled{$1});
$attributes_not_handled{$1} = 1;
# Get the name of the attribute file.
my $dir = "$HtmlDir/failures";
my $afile = "$dir/attribute_ignored_$1.txt";
# Only create another preprocessed file if the attribute file
# doesn't exist yet.
next if (-e $afile);
# Add this file to the list of files that contained this attribute.
# Generate a preprocessed file if we haven't already.
if (!(defined $ppfile)) {
$ppfile = ProcessClangFailure($Clang, $Lang, $file,
\@CmdArgsSansAnalyses,
$HtmlDir, $AttributeIgnored, $ofile);
}
mkpath $dir;
open(AFILE, ">$afile");
print AFILE "$ppfile\n";
close(AFILE);
}
close CHILD;
}
}
}
unlink($ofile);
}
##----------------------------------------------------------------------------##
# Lookup tables.
##----------------------------------------------------------------------------##
my %CompileOptionMap = (
'-nostdinc' => 0,
'-fblocks' => 0,
'-fno-builtin' => 0,
'-fobjc-gc-only' => 0,
'-fobjc-gc' => 0,
'-ffreestanding' => 0,
'-include' => 1,
'-idirafter' => 1,
'-imacros' => 1,
'-iprefix' => 1,
'-iquote' => 1,
'-isystem' => 1,
'-iwithprefix' => 1,
'-iwithprefixbefore' => 1
);
my %LinkerOptionMap = (
'-framework' => 1,
'-fobjc-link-runtime' => 0
);
my %CompilerLinkerOptionMap = (
'-fobjc-arc' => 0,
'-fno-objc-arc' => 0,
'-fobjc-abi-version' => 0, # This is really a 1 argument, but always has '='
'-fobjc-legacy-dispatch' => 0,
'-mios-simulator-version-min' => 0, # This really has 1 argument, but always has '='
'-isysroot' => 1,
'-arch' => 1,
'-m32' => 0,
'-m64' => 0,
'-stdlib' => 0, # This is really a 1 argument, but always has '='
'-v' => 0,
'-fpascal-strings' => 0,
'-mmacosx-version-min' => 0, # This is really a 1 argument, but always has '='
'-miphoneos-version-min' => 0 # This is really a 1 argument, but always has '='
);
my %IgnoredOptionMap = (
'-MT' => 1, # Ignore these preprocessor options.
'-MF' => 1,
'-fsyntax-only' => 0,
'-save-temps' => 0,
'-install_name' => 1,
'-exported_symbols_list' => 1,
'-current_version' => 1,
'-compatibility_version' => 1,
'-init' => 1,
'-e' => 1,
'-seg1addr' => 1,
'-bundle_loader' => 1,
'-multiply_defined' => 1,
'-sectorder' => 3,
'--param' => 1,
'-u' => 1,
'--serialize-diagnostics' => 1
);
my %LangMap = (
'c' => 'c',
'cp' => 'c++',
'cpp' => 'c++',
'cxx' => 'c++',
'txx' => 'c++',
'cc' => 'c++',
'C' => 'c++',
'ii' => 'c++',
'i' => 'c-cpp-output',
'm' => 'objective-c',
'mi' => 'objective-c-cpp-output',
'mm' => 'objective-c++'
);
my %UniqueOptions = (
'-isysroot' => 0
);
##----------------------------------------------------------------------------##
# Languages accepted.
##----------------------------------------------------------------------------##
my %LangsAccepted = (
"objective-c" => 1,
"c" => 1,
"c++" => 1,
"objective-c++" => 1
);
##----------------------------------------------------------------------------##
# Main Logic.
##----------------------------------------------------------------------------##
my $Action = 'link';
my @CompileOpts;
my @LinkOpts;
my @Files;
my $Lang;
my $Output;
my %Uniqued;
# Forward arguments to gcc.
my $Status = system($Compiler,@ARGV);
if (defined $ENV{'CCC_ANALYZER_LOG'}) {
print "$Compiler @ARGV\n";
}
if ($Status) { exit($Status >> 8); }
# Get the analysis options.
my $Analyses = $ENV{'CCC_ANALYZER_ANALYSIS'};
# Get the plugins to load.
my $Plugins = $ENV{'CCC_ANALYZER_PLUGINS'};
# Get the store model.
my $StoreModel = $ENV{'CCC_ANALYZER_STORE_MODEL'};
# Get the constraints engine.
my $ConstraintsModel = $ENV{'CCC_ANALYZER_CONSTRAINTS_MODEL'};
#Get the internal stats setting.
my $InternalStats = $ENV{'CCC_ANALYZER_INTERNAL_STATS'};
# Get the output format.
my $OutputFormat = $ENV{'CCC_ANALYZER_OUTPUT_FORMAT'};
if (!defined $OutputFormat) { $OutputFormat = "html"; }
# Determine the level of verbosity.
my $Verbose = 0;
if (defined $ENV{CCC_ANALYZER_VERBOSE}) { $Verbose = 1; }
if (defined $ENV{CCC_ANALYZER_LOG}) { $Verbose = 2; }
# Get the HTML output directory.
my $HtmlDir = $ENV{'CCC_ANALYZER_HTML'};
my %DisabledArchs = ('ppc' => 1, 'ppc64' => 1);
my %ArchsSeen;
my $HadArch = 0;
# Process the arguments.
foreach (my $i = 0; $i < scalar(@ARGV); ++$i) {
my $Arg = $ARGV[$i];
my ($ArgKey) = split /=/,$Arg,2;
# Modes ccc-analyzer supports
if ($Arg =~ /^-(E|MM?)$/) { $Action = 'preprocess'; }
elsif ($Arg eq '-c') { $Action = 'compile'; }
elsif ($Arg =~ /^-print-prog-name/) { exit 0; }
# Specially handle duplicate cases of -arch
if ($Arg eq "-arch") {
my $arch = $ARGV[$i+1];
# We don't want to process 'ppc' because of Clang's lack of support
# for Altivec (also some #defines won't likely be defined correctly, etc.)
if (!(defined $DisabledArchs{$arch})) { $ArchsSeen{$arch} = 1; }
$HadArch = 1;
++$i;
next;
}
# Options with possible arguments that should pass through to compiler.
if (defined $CompileOptionMap{$ArgKey}) {
my $Cnt = $CompileOptionMap{$ArgKey};
push @CompileOpts,$Arg;
while ($Cnt > 0) { ++$i; --$Cnt; push @CompileOpts, $ARGV[$i]; }
next;
}
if ($Arg =~ /-m.*/) {
push @CompileOpts,$Arg;
next;
}
# Handle the case where there isn't a space after -iquote
if ($Arg =~ /-iquote.*/) {
push @CompileOpts,$Arg;
next;
}
# Options with possible arguments that should pass through to linker.
if (defined $LinkerOptionMap{$ArgKey}) {
my $Cnt = $LinkerOptionMap{$ArgKey};
push @LinkOpts,$Arg;
while ($Cnt > 0) { ++$i; --$Cnt; push @LinkOpts, $ARGV[$i]; }
next;
}
# Options with possible arguments that should pass through to both compiler
# and the linker.
if (defined $CompilerLinkerOptionMap{$ArgKey}) {
my $Cnt = $CompilerLinkerOptionMap{$ArgKey};
# Check if this is an option that should have a unique value, and if so
# determine if the value was checked before.
if ($UniqueOptions{$Arg}) {
if (defined $Uniqued{$Arg}) {
$i += $Cnt;
next;
}
$Uniqued{$Arg} = 1;
}
push @CompileOpts,$Arg;
push @LinkOpts,$Arg;
while ($Cnt > 0) {
++$i; --$Cnt;
push @CompileOpts, $ARGV[$i];
push @LinkOpts, $ARGV[$i];
}
next;
}
# Ignored options.
if (defined $IgnoredOptionMap{$ArgKey}) {
my $Cnt = $IgnoredOptionMap{$ArgKey};
while ($Cnt > 0) {
++$i; --$Cnt;
}
next;
}
# Compile mode flags.
if ($Arg =~ /^-[D,I,U](.*)$/) {
my $Tmp = $Arg;
if ($1 eq '') {
# FIXME: Check if we are going off the end.
++$i;
$Tmp = $Arg . $ARGV[$i];
}
push @CompileOpts,$Tmp;
next;
}
# Language.
if ($Arg eq '-x') {
$Lang = $ARGV[$i+1];
++$i; next;
}
# Output file.
if ($Arg eq '-o') {
++$i;
$Output = $ARGV[$i];
next;
}
# Get the link mode.
if ($Arg =~ /^-[l,L,O]/) {
if ($Arg eq '-O') { push @LinkOpts,'-O1'; }
elsif ($Arg eq '-Os') { push @LinkOpts,'-O2'; }
else { push @LinkOpts,$Arg; }
next;
}
if ($Arg =~ /^-std=/) {
push @CompileOpts,$Arg;
next;
}
# if ($Arg =~ /^-f/) {
# # FIXME: Not sure if the remaining -fxxxx options have no arguments.
# push @CompileOpts,$Arg;
# push @LinkOpts,$Arg; # FIXME: Not sure if these are link opts.
# }
# Get the compiler/link mode.
if ($Arg =~ /^-F(.+)$/) {
my $Tmp = $Arg;
if ($1 eq '') {
# FIXME: Check if we are going off the end.
++$i;
$Tmp = $Arg . $ARGV[$i];
}
push @CompileOpts,$Tmp;
push @LinkOpts,$Tmp;
next;
}
# Input files.
if ($Arg eq '-filelist') {
# FIXME: Make sure we aren't walking off the end.
open(IN, $ARGV[$i+1]);
while (<IN>) { s/\015?\012//; push @Files,$_; }
close(IN);
++$i;
next;
}
# Handle -Wno-. We don't care about extra warnings, but
# we should suppress ones that we don't want to see.
if ($Arg =~ /^-Wno-/) {
push @CompileOpts, $Arg;
next;
}
if (!($Arg =~ /^-/)) {
push @Files, $Arg;
next;
}
}
if ($Action eq 'compile' or $Action eq 'link') {
my @Archs = keys %ArchsSeen;
# Skip the file if we don't support the architectures specified.
exit 0 if ($HadArch && scalar(@Archs) == 0);
foreach my $file (@Files) {
# Determine the language for the file.
my $FileLang = $Lang;
if (!defined($FileLang)) {
# Infer the language from the extension.
if ($file =~ /[.]([^.]+)$/) {
$FileLang = $LangMap{$1};
}
}
# FileLang still not defined? Skip the file.
next if (!defined $FileLang);
# Language not accepted?
next if (!defined $LangsAccepted{$FileLang});
my @CmdArgs;
my @AnalyzeArgs;
if ($FileLang ne 'unknown') {
push @CmdArgs, '-x', $FileLang;
}
if (defined $StoreModel) {
push @AnalyzeArgs, "-analyzer-store=$StoreModel";
}
if (defined $ConstraintsModel) {
push @AnalyzeArgs, "-analyzer-constraints=$ConstraintsModel";
}
if (defined $InternalStats) {
push @AnalyzeArgs, "-analyzer-stats";
}
if (defined $Analyses) {
push @AnalyzeArgs, split '\s+', $Analyses;
}
if (defined $Plugins) {
push @AnalyzeArgs, split '\s+', $Plugins;
}
if (defined $OutputFormat) {
push @AnalyzeArgs, "-analyzer-output=" . $OutputFormat;
if ($OutputFormat =~ /plist/) {
# Change "Output" to be a file.
my ($h, $f) = tempfile("report-XXXXXX", SUFFIX => ".plist",
DIR => $HtmlDir);
$ResultFile = $f;
# If the HtmlDir is not set, we sould clean up the plist files.
if (!defined $HtmlDir || -z $HtmlDir) {
$CleanupFile = $f;
}
}
}
push @CmdArgs, @CompileOpts;
push @CmdArgs, $file;
if (scalar @Archs) {
foreach my $arch (@Archs) {
my @NewArgs;
push @NewArgs, '-arch', $arch;
push @NewArgs, @CmdArgs;
Analyze($Clang, \@NewArgs, \@AnalyzeArgs, $FileLang, $Output,
$Verbose, $HtmlDir, $file);
}
}
else {
Analyze($Clang, \@CmdArgs, \@AnalyzeArgs, $FileLang, $Output,
$Verbose, $HtmlDir, $file);
}
}
}
exit($Status >> 8);

1660
static-analyzer/scan-build Executable file

File diff suppressed because it is too large Load Diff

131
static-analyzer/scan-view Executable file
View File

@@ -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()

View File

@@ -0,0 +1,62 @@
body { color:#000000; background-color:#ffffff }
body { font-family: Helvetica, sans-serif; font-size:9pt }
h1 { font-size: 14pt; }
h2 { font-size: 12pt; }
table { font-size:9pt }
table { border-spacing: 0px; border: 1px solid black }
th, table thead {
background-color:#eee; color:#666666;
font-weight: bold; cursor: default;
text-align:center;
font-weight: bold; font-family: Verdana;
white-space:nowrap;
}
.W { font-size:0px }
th, td { padding:5px; padding-left:8px; text-align:left }
td.SUMM_DESC { padding-left:12px }
td.DESC { white-space:pre }
td.Q { text-align:right }
td { text-align:left }
tbody.scrollContent { overflow:auto }
table.form_group {
background-color: #ccc;
border: 1px solid #333;
padding: 2px;
}
table.form_inner_group {
background-color: #ccc;
border: 1px solid #333;
padding: 0px;
}
table.form {
background-color: #999;
border: 1px solid #333;
padding: 2px;
}
td.form_label {
text-align: right;
vertical-align: top;
}
/* For one line entires */
td.form_clabel {
text-align: right;
vertical-align: center;
}
td.form_value {
text-align: left;
vertical-align: top;
}
td.form_submit {
text-align: right;
vertical-align: top;
}
h1.SubmitFail {
color: #f00;
}
h1.SubmitOk {
}

View File

@@ -0,0 +1,115 @@
#!/usr/bin/python
# [PR 11661] Note that we hardwire to /usr/bin/python because we
# want to the use the system version of Python on Mac OS X.
# This one has the scripting bridge enabled.
import sys
if sys.version_info < (2, 7):
print "set-xcode-analyzer requires Python 2.7 or later"
sys.exit(1)
import os
import subprocess
import re
import tempfile
import shutil
import stat
from AppKit import *
def FindClangSpecs(path):
print "(+) Searching for xcspec file in: ", path
for root, dirs, files in os.walk(path):
for f in files:
if f.endswith(".xcspec") and f.startswith("Clang LLVM"):
yield os.path.join(root, f)
def ModifySpec(path, isBuiltinAnalyzer, pathToChecker):
t = tempfile.NamedTemporaryFile(delete=False)
foundAnalyzer = False
with open(path) as f:
if isBuiltinAnalyzer:
# First search for CLANG_ANALYZER_EXEC. Newer
# versions of Xcode set EXEC_PATH to be CLANG_ANALYZER_EXEC.
with open(path) as f2:
for line in f2:
if line.find("CLANG_ANALYZER_EXEC") >= 0:
pathToChecker = "$(CLANG_ANALYZER_EXEC)"
break
# Now create a new file.
for line in f:
if not foundAnalyzer:
if line.find("Static Analyzer") >= 0:
foundAnalyzer = True
else:
m = re.search('^(\s*ExecPath\s*=\s*")', line)
if m:
line = "".join([m.group(0), pathToChecker, '";\n'])
# Do not modify further ExecPath's later in the xcspec.
foundAnalyzer = False
t.write(line)
t.close()
print "(+) processing:", path
try:
shutil.copy(t.name, path)
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
except IOError, why:
print " (-) Cannot update file:", why, "\n"
except OSError, why:
print " (-) Cannot update file:", why, "\n"
os.unlink(t.name)
def main():
from optparse import OptionParser
parser = OptionParser('usage: %prog [options]')
parser.set_description(__doc__)
parser.add_option("--use-checker-build", dest="path",
help="Use the Clang located at the provided absolute path, e.g. /Users/foo/checker-1")
parser.add_option("--use-xcode-clang", action="store_const",
const="$(CLANG)", dest="default",
help="Use the Clang bundled with Xcode")
(options, args) = parser.parse_args()
if options.path is None and options.default is None:
parser.error("You must specify a version of Clang to use for static analysis. Specify '-h' for details")
# determine if Xcode is running
for x in NSWorkspace.sharedWorkspace().runningApplications():
if x.localizedName().find("Xcode") >= 0:
print "(-) You must quit Xcode first before modifying its configuration files."
sys.exit(1)
isBuiltinAnalyzer = False
if options.path:
# Expand tildes.
path = os.path.expanduser(options.path)
if not path.endswith("clang"):
print "(+) Using Clang bundled with checker build:", path
path = os.path.join(path, "bin", "clang");
else:
print "(+) Using Clang located at:", path
else:
print "(+) Using the Clang bundled with Xcode"
path = options.default
isBuiltinAnalyzer = True
try:
xcode_path = subprocess.check_output(["xcode-select", "-print-path"])
except AttributeError:
# Fall back to the default install location when using Python < 2.7.0
xcode_path = "/Developer"
if (xcode_path.find(".app/") != -1):
# Cut off the 'Developer' dir, as the xcspec lies in another part
# of the Xcode.app subtree.
xcode_path = os.path.dirname(xcode_path)
foundSpec = False
for x in FindClangSpecs(xcode_path):
foundSpec = True
ModifySpec(x, isBuiltinAnalyzer, path)
if foundSpec == False:
print "(-) No compiler configuration file was found. Xcode's analyzer has not been updated."
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,493 @@
/*
SortTable
version 2
7th April 2007
Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
Instructions:
Download this file
Add <script src="sorttable.js"></script> to your HTML
Add class="sortable" to any table you'd like to make sortable
Click on the headers to sort
Thanks to many, many people for contributions and suggestions.
Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
This basically means: do what you want with it.
*/
var stIsIE = /*@cc_on!@*/false;
sorttable = {
init: function() {
// quit if this function has already been called
if (arguments.callee.done) return;
// flag this function so we don't do the same thing twice
arguments.callee.done = true;
// kill the timer
if (_timer) clearInterval(_timer);
if (!document.createElement || !document.getElementsByTagName) return;
sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
forEach(document.getElementsByTagName('table'), function(table) {
if (table.className.search(/\bsortable\b/) != -1) {
sorttable.makeSortable(table);
}
});
},
makeSortable: function(table) {
if (table.getElementsByTagName('thead').length == 0) {
// table doesn't have a tHead. Since it should have, create one and
// put the first table row in it.
the = document.createElement('thead');
the.appendChild(table.rows[0]);
table.insertBefore(the,table.firstChild);
}
// Safari doesn't support table.tHead, sigh
if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
if (table.tHead.rows.length != 1) return; // can't cope with two header rows
// Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
// "total" rows, for example). This is B&R, since what you're supposed
// to do is put them in a tfoot. So, if there are sortbottom rows,
// for backwards compatibility, move them to tfoot (creating it if needed).
sortbottomrows = [];
for (var i=0; i<table.rows.length; i++) {
if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
sortbottomrows[sortbottomrows.length] = table.rows[i];
}
}
if (sortbottomrows) {
if (table.tFoot == null) {
// table doesn't have a tfoot. Create one.
tfo = document.createElement('tfoot');
table.appendChild(tfo);
}
for (var i=0; i<sortbottomrows.length; i++) {
tfo.appendChild(sortbottomrows[i]);
}
delete sortbottomrows;
}
// work through each column and calculate its type
headrow = table.tHead.rows[0].cells;
for (var i=0; i<headrow.length; i++) {
// manually override the type with a sorttable_type attribute
if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
if (mtch) { override = mtch[1]; }
if (mtch && typeof sorttable["sort_"+override] == 'function') {
headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
} else {
headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
}
// make it clickable to sort
headrow[i].sorttable_columnindex = i;
headrow[i].sorttable_tbody = table.tBodies[0];
dean_addEvent(headrow[i],"click", function(e) {
if (this.className.search(/\bsorttable_sorted\b/) != -1) {
// if we're already sorted by this column, just
// reverse the table, which is quicker
sorttable.reverse(this.sorttable_tbody);
this.className = this.className.replace('sorttable_sorted',
'sorttable_sorted_reverse');
this.removeChild(document.getElementById('sorttable_sortfwdind'));
sortrevind = document.createElement('span');
sortrevind.id = "sorttable_sortrevind";
sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
this.appendChild(sortrevind);
return;
}
if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
// if we're already sorted by this column in reverse, just
// re-reverse the table, which is quicker
sorttable.reverse(this.sorttable_tbody);
this.className = this.className.replace('sorttable_sorted_reverse',
'sorttable_sorted');
this.removeChild(document.getElementById('sorttable_sortrevind'));
sortfwdind = document.createElement('span');
sortfwdind.id = "sorttable_sortfwdind";
sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
this.appendChild(sortfwdind);
return;
}
// remove sorttable_sorted classes
theadrow = this.parentNode;
forEach(theadrow.childNodes, function(cell) {
if (cell.nodeType == 1) { // an element
cell.className = cell.className.replace('sorttable_sorted_reverse','');
cell.className = cell.className.replace('sorttable_sorted','');
}
});
sortfwdind = document.getElementById('sorttable_sortfwdind');
if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
sortrevind = document.getElementById('sorttable_sortrevind');
if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
this.className += ' sorttable_sorted';
sortfwdind = document.createElement('span');
sortfwdind.id = "sorttable_sortfwdind";
sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
this.appendChild(sortfwdind);
// build an array to sort. This is a Schwartzian transform thing,
// i.e., we "decorate" each row with the actual sort key,
// sort based on the sort keys, and then put the rows back in order
// which is a lot faster because you only do getInnerText once per row
row_array = [];
col = this.sorttable_columnindex;
rows = this.sorttable_tbody.rows;
for (var j=0; j<rows.length; j++) {
row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
}
/* If you want a stable sort, uncomment the following line */
sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
/* and comment out this one */
//row_array.sort(this.sorttable_sortfunction);
tb = this.sorttable_tbody;
for (var j=0; j<row_array.length; j++) {
tb.appendChild(row_array[j][1]);
}
delete row_array;
});
}
}
},
guessType: function(table, column) {
// guess the type of a column based on its first non-blank row
sortfn = sorttable.sort_alpha;
for (var i=0; i<table.tBodies[0].rows.length; i++) {
text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
if (text != '') {
if (text.match(/^-?[<5B>$<24>]?[\d,.]+%?$/)) {
return sorttable.sort_numeric;
}
// check for a date: dd/mm/yyyy or dd/mm/yy
// can have / or . or - as separator
// can be mm/dd as well
possdate = text.match(sorttable.DATE_RE)
if (possdate) {
// looks like a date
first = parseInt(possdate[1]);
second = parseInt(possdate[2]);
if (first > 12) {
// definitely dd/mm
return sorttable.sort_ddmm;
} else if (second > 12) {
return sorttable.sort_mmdd;
} else {
// looks like a date, but we can't tell which, so assume
// that it's dd/mm (English imperialism!) and keep looking
sortfn = sorttable.sort_ddmm;
}
}
}
}
return sortfn;
},
getInnerText: function(node) {
// gets the text we want to use for sorting for a cell.
// strips leading and trailing whitespace.
// this is *not* a generic getInnerText function; it's special to sorttable.
// for example, you can override the cell text with a customkey attribute.
// it also gets .value for <input> fields.
hasInputs = (typeof node.getElementsByTagName == 'function') &&
node.getElementsByTagName('input').length;
if (node.getAttribute("sorttable_customkey") != null) {
return node.getAttribute("sorttable_customkey");
}
else if (typeof node.textContent != 'undefined' && !hasInputs) {
return node.textContent.replace(/^\s+|\s+$/g, '');
}
else if (typeof node.innerText != 'undefined' && !hasInputs) {
return node.innerText.replace(/^\s+|\s+$/g, '');
}
else if (typeof node.text != 'undefined' && !hasInputs) {
return node.text.replace(/^\s+|\s+$/g, '');
}
else {
switch (node.nodeType) {
case 3:
if (node.nodeName.toLowerCase() == 'input') {
return node.value.replace(/^\s+|\s+$/g, '');
}
case 4:
return node.nodeValue.replace(/^\s+|\s+$/g, '');
break;
case 1:
case 11:
var innerText = '';
for (var i = 0; i < node.childNodes.length; i++) {
innerText += sorttable.getInnerText(node.childNodes[i]);
}
return innerText.replace(/^\s+|\s+$/g, '');
break;
default:
return '';
}
}
},
reverse: function(tbody) {
// reverse the rows in a tbody
newrows = [];
for (var i=0; i<tbody.rows.length; i++) {
newrows[newrows.length] = tbody.rows[i];
}
for (var i=newrows.length-1; i>=0; i--) {
tbody.appendChild(newrows[i]);
}
delete newrows;
},
/* sort functions
each sort function takes two parameters, a and b
you are comparing a[0] and b[0] */
sort_numeric: function(a,b) {
aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
if (isNaN(aa)) aa = 0;
bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
if (isNaN(bb)) bb = 0;
return aa-bb;
},
sort_alpha: function(a,b) {
if (a[0]==b[0]) return 0;
if (a[0]<b[0]) return -1;
return 1;
},
sort_ddmm: function(a,b) {
mtch = a[0].match(sorttable.DATE_RE);
y = mtch[3]; m = mtch[2]; d = mtch[1];
if (m.length == 1) m = '0'+m;
if (d.length == 1) d = '0'+d;
dt1 = y+m+d;
mtch = b[0].match(sorttable.DATE_RE);
y = mtch[3]; m = mtch[2]; d = mtch[1];
if (m.length == 1) m = '0'+m;
if (d.length == 1) d = '0'+d;
dt2 = y+m+d;
if (dt1==dt2) return 0;
if (dt1<dt2) return -1;
return 1;
},
sort_mmdd: function(a,b) {
mtch = a[0].match(sorttable.DATE_RE);
y = mtch[3]; d = mtch[2]; m = mtch[1];
if (m.length == 1) m = '0'+m;
if (d.length == 1) d = '0'+d;
dt1 = y+m+d;
mtch = b[0].match(sorttable.DATE_RE);
y = mtch[3]; d = mtch[2]; m = mtch[1];
if (m.length == 1) m = '0'+m;
if (d.length == 1) d = '0'+d;
dt2 = y+m+d;
if (dt1==dt2) return 0;
if (dt1<dt2) return -1;
return 1;
},
shaker_sort: function(list, comp_func) {
// A stable sort function to allow multi-level sorting of data
// see: http://en.wikipedia.org/wiki/Cocktail_sort
// thanks to Joseph Nahmias
var b = 0;
var t = list.length - 1;
var swap = true;
while(swap) {
swap = false;
for(var i = b; i < t; ++i) {
if ( comp_func(list[i], list[i+1]) > 0 ) {
var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
swap = true;
}
} // for
t--;
if (!swap) break;
for(var i = t; i > b; --i) {
if ( comp_func(list[i], list[i-1]) < 0 ) {
var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
swap = true;
}
} // for
b++;
} // while(swap)
}
}
/* ******************************************************************
Supporting functions: bundled here to avoid depending on a library
****************************************************************** */
// Dean Edwards/Matthias Miller/John Resig
/* for Mozilla/Opera9 */
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", sorttable.init, false);
}
/* for Internet Explorer */
/*@cc_on @*/
/*@if (@_win32)
document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
if (this.readyState == "complete") {
sorttable.init(); // call the onload handler
}
};
/*@end @*/
/* for Safari */
if (/WebKit/i.test(navigator.userAgent)) { // sniff
var _timer = setInterval(function() {
if (/loaded|complete/.test(document.readyState)) {
sorttable.init(); // call the onload handler
}
}, 10);
}
/* for other browsers */
window.onload = sorttable.init;
// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini
// http://dean.edwards.name/weblog/2005/10/add-event/
function dean_addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
// assign each event handler a unique ID
if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
// create a hash table of event types for the element
if (!element.events) element.events = {};
// create a hash table of event handlers for each element/event pair
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
// store the existing event handler (if there is one)
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
// store the event handler in the hash table
handlers[handler.$$guid] = handler;
// assign a global event handler to do all the work
element["on" + type] = handleEvent;
}
};
// a counter used to create unique IDs
dean_addEvent.guid = 1;
function removeEvent(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else {
// delete the event handler from the hash table
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
}
};
function handleEvent(event) {
var returnValue = true;
// grab the event object (IE uses a global event object)
event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
// get a reference to the hash table of event handlers
var handlers = this.events[event.type];
// execute each event handler
for (var i in handlers) {
this.$$handleEvent = handlers[i];
if (this.$$handleEvent(event) === false) {
returnValue = false;
}
}
return returnValue;
};
function fixEvent(event) {
// add W3C standard event methods
event.preventDefault = fixEvent.preventDefault;
event.stopPropagation = fixEvent.stopPropagation;
return event;
};
fixEvent.preventDefault = function() {
this.returnValue = false;
};
fixEvent.stopPropagation = function() {
this.cancelBubble = true;
}
// Dean's forEach: http://dean.edwards.name/base/forEach.js
/*
forEach, version 1.0
Copyright 2006, Dean Edwards
License: http://www.opensource.org/licenses/mit-license.php
*/
// array-like enumeration
if (!Array.forEach) { // mozilla already supports this
Array.forEach = function(array, block, context) {
for (var i = 0; i < array.length; i++) {
block.call(context, array[i], i, array);
}
};
}
// generic enumeration
Function.prototype.forEach = function(object, block, context) {
for (var key in object) {
if (typeof this.prototype[key] == "undefined") {
block.call(context, object[key], key, object);
}
}
};
// character enumeration
String.forEach = function(string, block, context) {
Array.forEach(string.split(""), function(chr, index) {
block.call(context, chr, index, string);
});
};
// globally resolve forEach enumeration
var forEach = function(object, block, context) {
if (object) {
var resolve = Object; // default
if (object instanceof Function) {
// functions have a "length" property
resolve = Function;
} else if (object.forEach instanceof Function) {
// the object implements a custom forEach method so use that
object.forEach(block, context);
return;
} else if (typeof object == "string") {
// the object is a string
resolve = String;
} else if (typeof object.length == "number") {
// the object is array-like
resolve = Array;
}
resolve.forEach(object, block, context);
}
};

View File

@@ -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)