зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1031310 - remove xpcom/analysis/; r=bsmedberg
This commit is contained in:
Родитель
a328efc6da
Коммит
3a3b025f95
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
"""
|
||||
Upload a file attachment to MDC
|
||||
|
||||
Usage: python MDC-attach.py <file> <parent page name> <MIME type> <description>
|
||||
Please set MDC_USER and MDC_PASSWORD in the environment
|
||||
"""
|
||||
|
||||
import os, sys, deki
|
||||
|
||||
wikiuser = os.environ['MDC_USER']
|
||||
wikipw = os.environ['MDC_PASSWORD']
|
||||
|
||||
file, pageid, mimetype, description = sys.argv[1:]
|
||||
|
||||
wiki = deki.Deki("http://developer.mozilla.org/@api/deki/", wikiuser, wikipw)
|
||||
wiki.create_file(pageid, os.path.basename(file), open(file).read(), mimetype,
|
||||
description)
|
|
@ -1,22 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
"""
|
||||
Upload a page to MDC
|
||||
|
||||
Usage: python MDC-upload.py <file> <MDC-path>
|
||||
Please set MDC_USER and MDC_PASSWORD in the environment
|
||||
"""
|
||||
|
||||
import os, sys, deki
|
||||
|
||||
wikiuser = os.environ['MDC_USER']
|
||||
wikipw = os.environ['MDC_PASSWORD']
|
||||
|
||||
(file, wikipath) = sys.argv[1:]
|
||||
|
||||
wiki = deki.Deki("http://developer.mozilla.org/@api/deki/", wikiuser, wikipw)
|
||||
wiki.create_page(wikipath, open(file).read(), overwrite=True)
|
|
@ -1,44 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
DUMP_CLASSES = \
|
||||
nsAString_internal \
|
||||
nsACString_internal \
|
||||
$(NULL)
|
||||
|
||||
SPACE = $(NULL) $(NULL)
|
||||
COMMA = ,
|
||||
|
||||
HGREV = $(shell hg -R $(topsrcdir) id -i)
|
||||
|
||||
classapi: DEHYDRA_MODULES = $(srcdir)/type-printer.js
|
||||
classapi: TREEHYDRA_MODULES =
|
||||
classapi: DEHYDRA_ARGS += --dump-types=$(subst $(SPACE),$(COMMA),$(strip $(DUMP_CLASSES))) --rev=$(HGREV)
|
||||
classapi: $(call mkdir_deps,$(MDDEPDIR))
|
||||
$(CCC) $(OUTOPTION)/dev/null -c $(COMPILE_CXXFLAGS) $(srcdir)/type-printer.cpp >classapi.out 2>&1
|
||||
perl -e 'while (<>) {if (/DUMP-TYPE\((.*)\)/) {print "$$1 ";}}' <classapi.out >dumptypes.list
|
||||
perl -e 'while (<>) {if (/GRAPH-TYPE\((.*)\)/) {print "$$1 ";}}' <classapi.out >graphtypes.list
|
||||
$(EXIT_ON_ERROR) \
|
||||
for class in `cat graphtypes.list`; do \
|
||||
dot -Tpng -o$${class}-graph.png -Tcmapx -o$${class}-graph.map $${class}-graph.gv; \
|
||||
done
|
||||
$(EXIT_ON_ERROR) \
|
||||
for class in `cat dumptypes.list`; do \
|
||||
$(PYTHON) $(srcdir)/fix-srcrefs.py $(topsrcdir) < $${class}.html > $${class}-fixed.html; \
|
||||
done
|
||||
|
||||
upload_classapi:
|
||||
$(EXIT_ON_ERROR) \
|
||||
for class in `cat dumptypes.list`; do \
|
||||
$(PYTHON) $(srcdir)/MDC-upload.py $${class}-fixed.html en/$${class}; \
|
||||
done
|
||||
$(EXIT_ON_ERROR) \
|
||||
for class in `cat graphtypes.list`; do \
|
||||
$(PYTHON) $(srcdir)/MDC-attach.py $${class}-graph.png en/$${class} image/png 'Class inheritance graph'; \
|
||||
done
|
||||
|
||||
GARBAGE += $(wildcard *.html) $(wildcard *.png) $(wildcard *.map) \
|
||||
$(wildcard *.gv) classapi.out graphtypes.list dumptypes.list
|
|
@ -1,346 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
""" deki.py - Access the wiki pages on a MindTouch Deki server via the API.
|
||||
|
||||
Here's what this code can do:
|
||||
|
||||
wiki = deki.Deki("http://developer.mozilla.org/@api/deki/", username, password)
|
||||
page = wiki.get_page("Sheep")
|
||||
print page.title
|
||||
print page.doc.toxml()
|
||||
|
||||
page.title = "Bananas"
|
||||
page.save()
|
||||
|
||||
There are also some additional methods:
|
||||
wiki.create_page(path, content, title=, override=)
|
||||
wiki.move_page(old, new)
|
||||
wiki.get_subpages(page)
|
||||
|
||||
This module does not try to mimic the MindTouch "Plug" API. It's meant to be
|
||||
higher-level than that.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import urllib2, cookielib, httplib
|
||||
import xml.dom.minidom as dom
|
||||
from urllib import quote as _urllib_quote
|
||||
from urllib import urlencode as _urlencode
|
||||
import urlparse
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
__all__ = ['Deki']
|
||||
|
||||
|
||||
# === Utils
|
||||
|
||||
def _check(fact):
|
||||
if not fact:
|
||||
raise AssertionError('check failed')
|
||||
|
||||
def _urlquote(s, *args):
|
||||
return _urllib_quote(s.encode('utf-8'), *args)
|
||||
|
||||
def _make_url(*dirs, **params):
|
||||
""" dirs must already be url-encoded, params must not """
|
||||
url = '/'.join(dirs)
|
||||
if params:
|
||||
url += '?' + _urlencode(params)
|
||||
return url
|
||||
|
||||
class PutRequest(urllib2.Request):
|
||||
def get_method(self):
|
||||
return "PUT"
|
||||
|
||||
# === Dream framework client code
|
||||
|
||||
# This handler causes python to "always be logged in" when it's talking to the
|
||||
# server. If you're just accessing public pages, it generates more requests
|
||||
# than are strictly needed, but this is the behavior you want for a bot.
|
||||
#
|
||||
# The users/authenticate request is sent twice: once without any basic auth and
|
||||
# once with. Dumb. Feel free to fix.
|
||||
#
|
||||
class _LoginHandler(urllib2.HTTPCookieProcessor):
|
||||
def __init__(self, server):
|
||||
policy = cookielib.DefaultCookiePolicy(rfc2965=True)
|
||||
cookiejar = cookielib.CookieJar(policy)
|
||||
urllib2.HTTPCookieProcessor.__init__(self, cookiejar)
|
||||
self.server = server
|
||||
|
||||
def http_request(self, req):
|
||||
#print "DEBUG- Requesting " + req.get_full_url()
|
||||
s = self.server
|
||||
req = urllib2.HTTPCookieProcessor.http_request(self, req)
|
||||
if ('Cookie' not in req.unredirected_hdrs
|
||||
and req.get_full_url() != s.base + 'users/authenticate'):
|
||||
s.login()
|
||||
# Retry - should have a new cookie.
|
||||
req = urllib2.HTTPCookieProcessor.http_request(self, req)
|
||||
_check('Cookie' in req.unredirected_hdrs)
|
||||
return req
|
||||
|
||||
class DreamClient:
|
||||
def __init__(self, base, user, password):
|
||||
"""
|
||||
base - The base URI of the Deki API, with trailing slash.
|
||||
Typically, 'http://wiki.example.org/@api/deki/'.
|
||||
user, password - Your Deki login information.
|
||||
"""
|
||||
self.base = base
|
||||
pm = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||
pm.add_password(None, self.base, user, password)
|
||||
ah = urllib2.HTTPBasicAuthHandler(pm)
|
||||
lh = _LoginHandler(self)
|
||||
self._opener = urllib2.build_opener(ah, lh)
|
||||
|
||||
def login(self):
|
||||
response = self._opener.open(self.base + 'users/authenticate')
|
||||
response.close()
|
||||
|
||||
def open(self, url):
|
||||
return self._opener.open(self.base + url)
|
||||
|
||||
def _handleResponse(self, req):
|
||||
"""Helper method shared between post() and put()"""
|
||||
resp = self._opener.open(req)
|
||||
try:
|
||||
ct = resp.headers.get('Content-Type', '(none)')
|
||||
if '/xml' in ct or '+xml' in ct:
|
||||
return dom.parse(resp)
|
||||
else:
|
||||
#print "DEBUG- Content-Type:", ct
|
||||
crud = resp.read()
|
||||
#print 'DEBUG- crud:\n---\n%s\n---' % re.sub(r'(?m)^', ' ', crud)
|
||||
return None
|
||||
finally:
|
||||
resp.close()
|
||||
|
||||
|
||||
def post(self, url, data, type):
|
||||
#print "DEBUG- posting to:", self.base + url
|
||||
req = urllib2.Request(self.base + url, data, {'Content-Type': type})
|
||||
return self._handleResponse(req)
|
||||
|
||||
def put(self, url, data, type):
|
||||
#print "DEBUG- putting to:", self.base + url
|
||||
req = PutRequest(self.base + url, data, {'Content-Type': type})
|
||||
return self._handleResponse(req)
|
||||
|
||||
def get_xml(self, url):
|
||||
resp = self.open(url)
|
||||
try:
|
||||
return dom.parse(resp)
|
||||
finally:
|
||||
resp.close()
|
||||
|
||||
|
||||
# === DOM
|
||||
|
||||
def _text_of(node):
|
||||
if node.nodeType == node.ELEMENT_NODE:
|
||||
return u''.join(_text_of(n) for n in node.childNodes)
|
||||
elif node.nodeType == node.TEXT_NODE:
|
||||
return node.nodeValue
|
||||
else:
|
||||
return u''
|
||||
|
||||
def _the_element_by_name(doc, tagName):
|
||||
elts = doc.getElementsByTagName(tagName)
|
||||
if len(elts) != 1:
|
||||
raise ValueError("Expected exactly one <%s> tag, got %d." % (tagName, len(elts)))
|
||||
return elts[0]
|
||||
|
||||
def _first_element(node):
|
||||
n = node.firstChild
|
||||
while n is not None:
|
||||
if n.nodeType == n.ELEMENT_NODE:
|
||||
return n
|
||||
n = node.nextSibling
|
||||
return None
|
||||
|
||||
def _find_elements(node, path):
|
||||
if u'/' in path:
|
||||
[first, rest] = path.split(u'/', 1)
|
||||
for child in _find_elements(node, first):
|
||||
for desc in _find_elements(child, rest):
|
||||
yield desc
|
||||
else:
|
||||
for n in node.childNodes:
|
||||
if n.nodeType == node.ELEMENT_NODE and n.nodeName == path:
|
||||
yield n
|
||||
|
||||
|
||||
# === Deki
|
||||
|
||||
def _format_page_id(id):
|
||||
if isinstance(id, int):
|
||||
return str(id)
|
||||
elif id is Deki.HOME:
|
||||
return 'home'
|
||||
elif isinstance(id, basestring):
|
||||
# Double-encoded, per the Deki API reference.
|
||||
return '=' + _urlquote(_urlquote(id, ''))
|
||||
|
||||
class Deki(DreamClient):
|
||||
HOME = object()
|
||||
|
||||
def get_page(self, page_id):
|
||||
""" Get the content of a page from the wiki.
|
||||
|
||||
The page_id argument must be one of:
|
||||
an int - The page id (an arbitrary number assigned by Deki)
|
||||
a str - The page name (not the title, the full path that shows up in the URL)
|
||||
Deki.HOME - Refers to the main page of the wiki.
|
||||
|
||||
Returns a Page object.
|
||||
"""
|
||||
p = Page(self)
|
||||
p._load(page_id)
|
||||
return p
|
||||
|
||||
def create_page(self, path, content, title=None, overwrite=False):
|
||||
""" Create a new wiki page.
|
||||
|
||||
Parameters:
|
||||
path - str - The page id.
|
||||
content - str - The XML content to put in the new page.
|
||||
The document element must be a <body>.
|
||||
title - str - The page title. Keyword argument only.
|
||||
Defaults to the last path-segment of path.
|
||||
overwrite - bool - Whether to overwrite an existing page. If false,
|
||||
and the page already exists, the method will throw an error.
|
||||
"""
|
||||
if title is None:
|
||||
title = path.split('/')[-1]
|
||||
doc = dom.parseString(content)
|
||||
_check(doc.documentElement.tagName == 'body')
|
||||
p = Page(self)
|
||||
p._create(path, title, doc, overwrite)
|
||||
|
||||
def attach_file(self, page, name, data, mimetype, description=None):
|
||||
"""Create or update a file attachment.
|
||||
|
||||
Parameters:
|
||||
page - str - the page ID this file is related to
|
||||
name - str - the name of the file
|
||||
data - str - the file data
|
||||
mimetype - str - the MIME type of the file
|
||||
description - str - a description of the file
|
||||
"""
|
||||
|
||||
p = {}
|
||||
if description is not None:
|
||||
p['description'] = description
|
||||
|
||||
url = _make_url('pages', _format_page_id(page),
|
||||
'files', _format_page_id(name), **p)
|
||||
|
||||
r = self.put(url, data, mimetype)
|
||||
_check(r.documentElement.nodeName == u'file')
|
||||
|
||||
def get_subpages(self, page_id):
|
||||
""" Return the ids of all subpages of the given page. """
|
||||
doc = self.get_xml(_make_url("pages", _format_page_id(page_id),
|
||||
"files,subpages"))
|
||||
for elt in _find_elements(doc, u'page/subpages/page.subpage/path'):
|
||||
yield _text_of(elt)
|
||||
|
||||
def move_page(self, page_id, new_title, redirects=True):
|
||||
""" Move an existing page to a new location.
|
||||
|
||||
A page cannot be moved to a destination that already exists, is a
|
||||
descendant, or has a protected title (ex. Special:xxx, User:,
|
||||
Template:).
|
||||
|
||||
When a page is moved, subpages under the specified page are also moved.
|
||||
For each moved page, the system automatically creates an alias page
|
||||
that redirects from the old to the new destination.
|
||||
"""
|
||||
self.post(_make_url("pages", _format_page_id(page_id), "move",
|
||||
to=new_title,
|
||||
redirects=redirects and "1" or "0"),
|
||||
"", "text/plain")
|
||||
|
||||
class Page:
|
||||
""" A Deki wiki page.
|
||||
|
||||
To obtain a page, call wiki.get_page(id).
|
||||
Attributes:
|
||||
title : unicode - The page title.
|
||||
doc : Document - The content of the page as a DOM Document.
|
||||
The root element of this document is a <body>.
|
||||
path : unicode - The path. Use this to detect redirects, as otherwise
|
||||
page.save() will overwrite the redirect with a copy of the content!
|
||||
deki : Deki - The Deki object from which the page was loaded.
|
||||
page_id : str/id/Deki.HOME - The page id used to load the page.
|
||||
load_time : datetime - The time the page was loaded,
|
||||
according to the clock on the client machine.
|
||||
Methods:
|
||||
save() - Save the modified document back to the server.
|
||||
Only the page.title and the contents of page.doc are saved.
|
||||
"""
|
||||
|
||||
def __init__(self, deki):
|
||||
self.deki = deki
|
||||
|
||||
def _create(self, path, title, doc, overwrite):
|
||||
self.title = title
|
||||
self.doc = doc
|
||||
self.page_id = path
|
||||
if overwrite:
|
||||
self.load_time = datetime(2500, 1, 1)
|
||||
else:
|
||||
self.load_time = datetime(1900, 1, 1)
|
||||
self.path = path
|
||||
self.save()
|
||||
|
||||
def _load(self, page_id):
|
||||
""" page_id - See comment near the definition of `HOME`. """
|
||||
load_time = datetime.utcnow()
|
||||
|
||||
# Getting the title is a whole separate query!
|
||||
url = 'pages/%s/info' % _format_page_id(page_id)
|
||||
doc = self.deki.get_xml(url)
|
||||
title = _text_of(_the_element_by_name(doc, 'title'))
|
||||
path = _text_of(_the_element_by_name(doc, 'path'))
|
||||
|
||||
# If you prefer to sling regexes, you can request format=raw instead.
|
||||
# The result is an XML document with one big fat text node in the body.
|
||||
url = _make_url('pages', _format_page_id(page_id), 'contents',
|
||||
format='xhtml', mode='edit')
|
||||
doc = self.deki.get_xml(url)
|
||||
|
||||
content = doc.documentElement
|
||||
_check(content.tagName == u'content')
|
||||
body = _first_element(content)
|
||||
_check(body is not None)
|
||||
_check(body.tagName == u'body')
|
||||
|
||||
doc.removeChild(content)
|
||||
doc.appendChild(body)
|
||||
|
||||
self.page_id = page_id
|
||||
self.load_time = load_time
|
||||
self.title = title
|
||||
self.path = path
|
||||
self.doc = doc
|
||||
|
||||
def save(self):
|
||||
p = {'edittime': _urlquote(self.load_time.strftime('%Y%m%d%H%M%S')),
|
||||
'abort': 'modified'}
|
||||
|
||||
if self.title is not None:
|
||||
p['title'] = _urlquote(self.title)
|
||||
|
||||
url = _make_url('pages', _format_page_id(self.page_id), 'contents', **p)
|
||||
|
||||
body = self.doc.documentElement
|
||||
bodyInnerXML = ''.join(n.toxml('utf-8') for n in body.childNodes)
|
||||
|
||||
reply = self.deki.post(url, bodyInnerXML, 'text/plain; charset=utf-8')
|
||||
_check(reply.documentElement.nodeName == u'edit')
|
||||
_check(reply.documentElement.getAttribute(u'status') == u'success')
|
|
@ -1,41 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
"""
|
||||
Fix references to source files of the form [LOCpath]
|
||||
so that they are relative to a given source directory.
|
||||
|
||||
Substitute the DOT-generated image map into the document.
|
||||
"""
|
||||
|
||||
import os, sys, re
|
||||
|
||||
(srcdir, ) = sys.argv[1:]
|
||||
srcdir = os.path.realpath(srcdir)
|
||||
|
||||
f = re.compile(r'\[LOC(.*?)\]')
|
||||
|
||||
def replacer(m):
|
||||
file = m.group(1)
|
||||
file = os.path.realpath(file)
|
||||
if not file.startswith(srcdir):
|
||||
raise Exception("File %s doesn't start with %s" % (file, srcdir))
|
||||
|
||||
file = file[len(srcdir) + 1:]
|
||||
return file
|
||||
|
||||
s = re.compile(r'\[MAP(.*?)\]')
|
||||
|
||||
def mapreplace(m):
|
||||
file = m.group(1)
|
||||
c = open(file).read()
|
||||
return c
|
||||
|
||||
for line in sys.stdin:
|
||||
line = f.sub(replacer, line)
|
||||
line = s.sub(mapreplace, line)
|
||||
|
||||
sys.stdout.write(line)
|
|
@ -1,150 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
require({ version: '1.8' });
|
||||
require({ after_gcc_pass: 'cfg' });
|
||||
|
||||
include('treehydra.js');
|
||||
|
||||
include('util.js');
|
||||
include('gcc_util.js');
|
||||
include('gcc_print.js');
|
||||
include('unstable/adts.js');
|
||||
include('unstable/analysis.js');
|
||||
include('unstable/esp.js');
|
||||
|
||||
/* This implements the control flows-through analysis in bug 432917 */
|
||||
var Zero_NonZero = {}
|
||||
include('unstable/zero_nonzero.js', Zero_NonZero);
|
||||
|
||||
MapFactory.use_injective = true;
|
||||
|
||||
// Print a trace for each function analyzed
|
||||
let TRACE_FUNCTIONS = 0;
|
||||
// Trace operation of the ESP analysis, use 2 or 3 for more detail
|
||||
let TRACE_ESP = 0;
|
||||
// Print time-taken stats
|
||||
let TRACE_PERF = 0;
|
||||
|
||||
function process_tree(fndecl) {
|
||||
// At this point we have a function we want to analyze
|
||||
if (TRACE_FUNCTIONS) {
|
||||
print('* function ' + decl_name(fndecl));
|
||||
print(' ' + loc_string(location_of(fndecl)));
|
||||
}
|
||||
if (TRACE_PERF) timer_start(fstring);
|
||||
|
||||
let cfg = function_decl_cfg(fndecl);
|
||||
|
||||
try {
|
||||
let trace = TRACE_ESP;
|
||||
let a = new FlowCheck(cfg, trace);
|
||||
a.run();
|
||||
} catch (e if e == "skip") {
|
||||
return
|
||||
}
|
||||
print("checked " + decl_name(fndecl))
|
||||
if (cfg.x_exit_block_ptr.stateIn.substates)
|
||||
for each (let substate in cfg.x_exit_block_ptr.stateIn.substates.getValues()) {
|
||||
for each (let v in substate.getVariables()) {
|
||||
let var_state= substate.get (v)
|
||||
let blame = substate.getBlame(v)
|
||||
if (var_state != ESP.TOP && typeof var_state == 'string')
|
||||
error(decl_name(fndecl) + ": Control did not flow through " +var_state, location_of(blame))
|
||||
}
|
||||
}
|
||||
|
||||
if (TRACE_PERF) timer_stop(fstring);
|
||||
}
|
||||
|
||||
let track_return_loc = 0;
|
||||
const FLOW_THROUGH = "MUST_FLOW_THROUGH"
|
||||
|
||||
function FlowCheck(cfg, trace) {
|
||||
let found = create_decl_set(); // ones we already found
|
||||
for (let bb in cfg_bb_iterator(cfg)) {
|
||||
for (let isn in bb_isn_iterator(bb)) {
|
||||
switch (isn.tree_code()) {
|
||||
case GIMPLE_CALL: {
|
||||
let fn = gimple_call_fndecl(isn)
|
||||
if (!fn || decl_name(fn) != FLOW_THROUGH)
|
||||
continue;
|
||||
this.must_flow_fn = fn
|
||||
break
|
||||
}
|
||||
case GIMPLE_RETURN:
|
||||
let ret_expr = return_expr(isn);
|
||||
if (track_return_loc && ret_expr) {
|
||||
TREE_CHECK(ret_expr, VAR_DECL, RESULT_DECL);
|
||||
this.rval = ret_expr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.must_flow_fn)
|
||||
throw "skip"
|
||||
|
||||
let psvar_list = [new ESP.PropVarSpec(this.must_flow_fn, true)]
|
||||
|
||||
if (this.rval)
|
||||
psvar_list.push(new ESP.PropVarSpec(this.rval))
|
||||
|
||||
this.zeroNonzero = new Zero_NonZero.Zero_NonZero()
|
||||
ESP.Analysis.call(this, cfg, psvar_list, Zero_NonZero.meet, trace);
|
||||
}
|
||||
|
||||
FlowCheck.prototype = new ESP.Analysis;
|
||||
|
||||
function char_star_arg2string(tree) {
|
||||
return TREE_STRING_POINTER(tree.tree_check(ADDR_EXPR).operands()[0].tree_check(ARRAY_REF).operands()[0])
|
||||
}
|
||||
|
||||
// State transition function. Mostly, we delegate everything to
|
||||
// another function as either an assignment or a call.
|
||||
FlowCheck.prototype.flowState = function(isn, state) {
|
||||
switch (TREE_CODE(isn)) {
|
||||
case GIMPLE_CALL: {
|
||||
let fn = gimple_call_fndecl(isn)
|
||||
if (fn == this.must_flow_fn)
|
||||
state.assignValue(fn, char_star_arg2string(gimple_call_arg(isn, 0)), isn)
|
||||
break
|
||||
}
|
||||
case GIMPLE_LABEL: {
|
||||
let label = decl_name(gimple_op(isn, 0))
|
||||
for ([value, blame] in state.yieldPreconditions(this.must_flow_fn)) {
|
||||
if (label != value) continue
|
||||
// reached the goto label we wanted =D
|
||||
state.assignValue(this.must_flow_fn, ESP.TOP, isn)
|
||||
}
|
||||
break
|
||||
}
|
||||
case GIMPLE_RETURN:
|
||||
for ([value, blame] in state.yieldPreconditions(this.must_flow_fn)) {
|
||||
if (typeof value != 'string') continue
|
||||
let loc;
|
||||
if (this.rval)
|
||||
for ([value, blame] in state.yieldPreconditions(this.rval)) {
|
||||
loc = value
|
||||
break
|
||||
}
|
||||
error("return without going through label "+ value, loc);
|
||||
return
|
||||
}
|
||||
break
|
||||
case GIMPLE_ASSIGN:
|
||||
if (track_return_loc && gimple_op(isn, 0) == this.rval) {
|
||||
state.assignValue(this.rval, location_of(isn), isn)
|
||||
break
|
||||
}
|
||||
default:
|
||||
this.zeroNonzero.flowState(isn, state)
|
||||
}
|
||||
}
|
||||
|
||||
// State transition function to apply branch filters. This is kind
|
||||
// of boilerplate--we're just handling some stuff that GCC generates.
|
||||
FlowCheck.prototype.flowStateCond = function(isn, truth, state) {
|
||||
this.zeroNonzero.flowStateCond (isn, truth, state)
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* May-return analysis.
|
||||
* This makes sense only for functions that return a value. The analysis
|
||||
* determines the set of variables that may transitively reach the return
|
||||
* statement. */
|
||||
|
||||
function MayReturnAnalysis() {
|
||||
BackwardAnalysis.apply(this, arguments);
|
||||
// May-return variables. We collect them all here.
|
||||
this.vbls = create_decl_set();
|
||||
// The return value variable itself
|
||||
this.retvar = undefined;
|
||||
}
|
||||
|
||||
MayReturnAnalysis.prototype = new BackwardAnalysis;
|
||||
|
||||
MayReturnAnalysis.prototype.flowState = function(isn, state) {
|
||||
if (TREE_CODE(isn) == GIMPLE_RETURN) {
|
||||
let v = return_expr(isn);
|
||||
if (!v)
|
||||
return;
|
||||
if (v.tree_code() == RESULT_DECL) // only an issue with 4.3
|
||||
throw new Error("Weird case hit");
|
||||
this.vbls.add(v);
|
||||
state.add(v);
|
||||
this.retvar = v;
|
||||
} else if (TREE_CODE(isn) == GIMPLE_ASSIGN) {
|
||||
let lhs = gimple_op(isn, 0);
|
||||
let rhs = gimple_op(isn, 1);
|
||||
if (DECL_P(rhs) && DECL_P(lhs) && state.has(lhs)) {
|
||||
this.vbls.add(rhs);
|
||||
state.add(rhs);
|
||||
}
|
||||
|
||||
for (let e in isn_defs(isn, 'strong')) {
|
||||
if (DECL_P(e)) {
|
||||
state.remove(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEFINES['MOZILLA_INTERNAL_API'] = True
|
|
@ -1,52 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Detect classes that should have overridden members of their parent
|
||||
* classes but didn't.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* struct S {
|
||||
* virtual NS_MUST_OVERRIDE void f();
|
||||
* virtual void g();
|
||||
* };
|
||||
*
|
||||
* struct A : S { virtual void f(); }; // ok
|
||||
* struct B : S { virtual NS_MUST_OVERRIDE void f(); }; // also ok
|
||||
*
|
||||
* struct C : S { virtual void g(); }; // ERROR: must override f()
|
||||
* struct D : S { virtual void f(int); }; // ERROR: different overload
|
||||
* struct E : A { }; // ok: A's definition of f() is good for subclasses
|
||||
* struct F : B { }; // ERROR: B's definition of f() is still must-override
|
||||
*
|
||||
* We don't care if you define the method or not.
|
||||
*/
|
||||
|
||||
function get_must_overrides(cls)
|
||||
{
|
||||
let mos = {};
|
||||
for each (let base in cls.bases)
|
||||
for each (let m in base.type.members)
|
||||
if (hasAttribute(m, 'NS_must_override'))
|
||||
mos[m.shortName] = m;
|
||||
|
||||
return mos;
|
||||
}
|
||||
|
||||
function process_type(t)
|
||||
{
|
||||
if (t.isIncomplete || (t.kind != 'class' && t.kind != 'struct'))
|
||||
return;
|
||||
|
||||
let mos = get_must_overrides(t);
|
||||
for each (let m in t.members) {
|
||||
let mos_m = mos[m.shortName]
|
||||
if (mos_m && signaturesMatch(mos_m, m))
|
||||
delete mos[m.shortName];
|
||||
}
|
||||
|
||||
for each (let u in mos)
|
||||
error(t.kind + " " + t.name + " must override " + u.name, t.loc);
|
||||
}
|
|
@ -1,880 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
require({ version: '1.8' });
|
||||
require({ after_gcc_pass: 'cfg' });
|
||||
|
||||
include('treehydra.js');
|
||||
|
||||
include('util.js');
|
||||
include('gcc_util.js');
|
||||
include('gcc_print.js');
|
||||
include('unstable/adts.js');
|
||||
include('unstable/analysis.js');
|
||||
include('unstable/esp.js');
|
||||
let Zero_NonZero = {};
|
||||
include('unstable/zero_nonzero.js', Zero_NonZero);
|
||||
|
||||
include('xpcom/analysis/mayreturn.js');
|
||||
|
||||
function safe_location_of(t) {
|
||||
if (t === undefined)
|
||||
return UNKNOWN_LOCATION;
|
||||
|
||||
return location_of(t);
|
||||
}
|
||||
|
||||
MapFactory.use_injective = true;
|
||||
|
||||
// Print a trace for each function analyzed
|
||||
let TRACE_FUNCTIONS = 0;
|
||||
// Trace operation of the ESP analysis, use 2 or 3 for more detail
|
||||
let TRACE_ESP = 0;
|
||||
// Trace determination of function call parameter semantics, 2 for detail
|
||||
let TRACE_CALL_SEM = 0;
|
||||
// Print time-taken stats
|
||||
let TRACE_PERF = 0;
|
||||
// Log analysis results in a special format
|
||||
let LOG_RESULTS = false;
|
||||
|
||||
const WARN_ON_SET_NULL = false;
|
||||
const WARN_ON_SET_FAILURE = false;
|
||||
|
||||
// Filter functions to process per CLI
|
||||
let func_filter;
|
||||
if (this.arg == undefined || this.arg == '') {
|
||||
func_filter = function(fd) true;
|
||||
} else {
|
||||
func_filter = function(fd) function_decl_name(fd) == this.arg;
|
||||
}
|
||||
|
||||
function process_tree(func_decl) {
|
||||
if (!func_filter(func_decl)) return;
|
||||
|
||||
// Determine outparams and return if function not relevant
|
||||
if (DECL_CONSTRUCTOR_P(func_decl)) return;
|
||||
let psem = OutparamCheck.prototype.func_param_semantics(func_decl);
|
||||
if (!psem.some(function(x) x.check)) return;
|
||||
let decl = rectify_function_decl(func_decl);
|
||||
if (decl.resultType != 'nsresult' && decl.resultType != 'PRBool' &&
|
||||
decl.resultType != 'void') {
|
||||
warning("Cannot analyze outparam usage for function with return type '" +
|
||||
decl.resultType + "'", location_of(func_decl));
|
||||
return;
|
||||
}
|
||||
|
||||
let params = [ v for (v in flatten_chain(DECL_ARGUMENTS(func_decl))) ];
|
||||
let outparam_list = [];
|
||||
let psem_list = [];
|
||||
for (let i = 0; i < psem.length; ++i) {
|
||||
if (psem[i].check) {
|
||||
outparam_list.push(params[i]);
|
||||
psem_list.push(psem[i]);
|
||||
}
|
||||
}
|
||||
if (outparam_list.length == 0) return;
|
||||
|
||||
// At this point we have a function we want to analyze
|
||||
let fstring = rfunc_string(decl);
|
||||
if (TRACE_FUNCTIONS) {
|
||||
print('* function ' + fstring);
|
||||
print(' ' + loc_string(location_of(func_decl)));
|
||||
}
|
||||
if (TRACE_PERF) timer_start(fstring);
|
||||
for (let i = 0; i < outparam_list.length; ++i) {
|
||||
let p = outparam_list[i];
|
||||
if (TRACE_FUNCTIONS) {
|
||||
print(" outparam " + expr_display(p) + " " + DECL_UID(p) + ' ' +
|
||||
psem_list[i].label);
|
||||
}
|
||||
}
|
||||
|
||||
let cfg = function_decl_cfg(func_decl);
|
||||
|
||||
let [retvar, retvars] = function() {
|
||||
let trace = 0;
|
||||
let a = new MayReturnAnalysis(cfg, trace);
|
||||
a.run();
|
||||
return [a.retvar, a.vbls];
|
||||
}();
|
||||
if (retvar == undefined && decl.resultType != 'void') throw new Error("assert");
|
||||
|
||||
{
|
||||
let trace = TRACE_ESP;
|
||||
for (let i = 0; i < outparam_list.length; ++i) {
|
||||
let psem = [ psem_list[i] ];
|
||||
let outparam = [ outparam_list[i] ];
|
||||
let a = new OutparamCheck(cfg, psem, outparam, retvar, retvars, trace);
|
||||
// This is annoying, but this field is only used for logging anyway.
|
||||
a.fndecl = func_decl;
|
||||
a.run();
|
||||
a.check(decl.resultType == 'void', func_decl);
|
||||
}
|
||||
}
|
||||
|
||||
if (TRACE_PERF) timer_stop(fstring);
|
||||
}
|
||||
|
||||
// Outparam check analysis
|
||||
function OutparamCheck(cfg, psem_list, outparam_list, retvar, retvar_set,
|
||||
trace) {
|
||||
// We need to save the retvars so we can detect assignments through
|
||||
// their addresses passed as arguments.
|
||||
this.retvar_set = retvar_set;
|
||||
this.retvar = retvar;
|
||||
|
||||
// We need both an ordered set and a lookup structure
|
||||
this.outparam_list = outparam_list
|
||||
this.outparams = create_decl_set(outparam_list);
|
||||
this.psem_list = psem_list;
|
||||
|
||||
// Set up property state vars for ESP
|
||||
let psvar_list = [];
|
||||
for each (let v in outparam_list) {
|
||||
psvar_list.push(new ESP.PropVarSpec(v, true, av.NOT_WRITTEN));
|
||||
}
|
||||
for (let v in retvar_set.items()) {
|
||||
psvar_list.push(new ESP.PropVarSpec(v, v == this.retvar, ESP.TOP));
|
||||
}
|
||||
if (trace) {
|
||||
print("PS vars");
|
||||
for each (let v in this.psvar_list) {
|
||||
print(" " + expr_display(v.vbl));
|
||||
}
|
||||
}
|
||||
this.zeroNonzero = new Zero_NonZero.Zero_NonZero();
|
||||
ESP.Analysis.call(this, cfg, psvar_list, av.meet, trace);
|
||||
}
|
||||
|
||||
// Abstract values for outparam check
|
||||
function AbstractValue(name, ch) {
|
||||
this.name = name;
|
||||
this.ch = ch;
|
||||
}
|
||||
|
||||
AbstractValue.prototype.equals = function(v) {
|
||||
return this === v;
|
||||
}
|
||||
|
||||
AbstractValue.prototype.toString = function() {
|
||||
return this.name + ' (' + this.ch + ')';
|
||||
}
|
||||
|
||||
AbstractValue.prototype.toShortString = function() {
|
||||
return this.ch;
|
||||
}
|
||||
|
||||
let avspec = [
|
||||
// Abstract values for outparam contents write status
|
||||
[ 'NULL', 'x' ], // is a null pointer
|
||||
[ 'NOT_WRITTEN', '-' ], // not written
|
||||
[ 'WROTE_NULL', '/' ], // had NULL written to
|
||||
[ 'WRITTEN', '+' ], // had anything written to
|
||||
// MAYBE_WRITTEN is special. "Officially", it means the same thing as
|
||||
// NOT_WRITTEN. What it really means is that an outparam was passed
|
||||
// to another function as a possible outparam (outparam type, but not
|
||||
// in last position), so if there is an error with it not being written,
|
||||
// we can give a hint about the possible outparam in the warning.
|
||||
[ 'MAYBE_WRITTEN', '?' ], // written if possible outparam is one
|
||||
];
|
||||
|
||||
let av = {};
|
||||
for each (let [name, ch] in avspec) {
|
||||
av[name] = new AbstractValue(name, ch);
|
||||
}
|
||||
|
||||
av.ZERO = Zero_NonZero.Lattice.ZERO;
|
||||
av.NONZERO = Zero_NonZero.Lattice.NONZERO;
|
||||
|
||||
/*
|
||||
av.ZERO.negation = av.NONZERO;
|
||||
av.NONZERO.negation = av.ZERO;
|
||||
|
||||
// Abstract values for int constants. We use these to figure out feasible
|
||||
// paths in the presence of GCC finally_tmp-controlled switches.
|
||||
function makeIntAV(v) {
|
||||
let key = 'int_' + v;
|
||||
if (cachedAVs.hasOwnProperty(key)) return cachedAVs[key];
|
||||
|
||||
let s = "" + v;
|
||||
let ans = cachedAVs[key] = new AbstractValue(s, s);
|
||||
ans.int_val = v;
|
||||
return ans;
|
||||
}
|
||||
*/
|
||||
|
||||
let cachedAVs = {};
|
||||
|
||||
// Abstract values for pointers that contain a copy of an outparam
|
||||
// pointer. We use these to figure out writes to a casted copy of
|
||||
// an outparam passed to another method.
|
||||
function makeOutparamAV(v) {
|
||||
let key = 'outparam_' + DECL_UID(v);
|
||||
if (key in cachedAVs) return cachedAVs[key];
|
||||
|
||||
let ans = cachedAVs[key] =
|
||||
new AbstractValue('OUTPARAM:' + expr_display(v), 'P');
|
||||
ans.outparam = v;
|
||||
return ans;
|
||||
}
|
||||
|
||||
/** Return the integer value if this is an integer av, otherwise undefined. */
|
||||
av.intVal = function(v) {
|
||||
if (v.hasOwnProperty('int_val'))
|
||||
return v.int_val;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Meet function for our abstract values. */
|
||||
av.meet = function(v1, v2) {
|
||||
// At this point we know v1 != v2.
|
||||
let values = [v1,v2]
|
||||
if (values.indexOf(av.LOCKED) != -1
|
||||
|| values.indexOf(av.UNLOCKED) != -1)
|
||||
return ESP.NOT_REACHED;
|
||||
|
||||
return Zero_NonZero.meet(v1, v2)
|
||||
};
|
||||
|
||||
// Outparam check analysis
|
||||
OutparamCheck.prototype = new ESP.Analysis;
|
||||
|
||||
OutparamCheck.prototype.split = function(vbl, v) {
|
||||
// Can't happen for current version of ESP, but could change
|
||||
if (v != ESP.TOP) throw new Error("not implemented");
|
||||
return [ av.ZERO, av.NONZERO ];
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.updateEdgeState = function(e) {
|
||||
e.state.keepOnly(e.dest.keepVars);
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.flowState = function(isn, state) {
|
||||
switch (TREE_CODE(isn)) {
|
||||
case GIMPLE_ASSIGN:
|
||||
this.processAssign(isn, state);
|
||||
break;
|
||||
case GIMPLE_CALL:
|
||||
this.processCall(isn, isn, state);
|
||||
break;
|
||||
case GIMPLE_SWITCH:
|
||||
case GIMPLE_COND:
|
||||
// This gets handled by flowStateCond instead, has no exec effect
|
||||
break;
|
||||
default:
|
||||
this.zeroNonzero.flowState(isn, state);
|
||||
}
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.flowStateCond = function(isn, truth, state) {
|
||||
this.zeroNonzero.flowStateCond(isn, truth, state);
|
||||
}
|
||||
|
||||
// For any outparams-specific semantics, we handle it here and then
|
||||
// return. Otherwise we delegate to the zero-nonzero analysis.
|
||||
OutparamCheck.prototype.processAssign = function(isn, state) {
|
||||
let lhs = gimple_op(isn, 0);
|
||||
let rhs = gimple_op(isn, 1);
|
||||
|
||||
if (DECL_P(lhs)) {
|
||||
// Unwrap NOP_EXPR, which is semantically a copy.
|
||||
if (TREE_CODE(rhs) == NOP_EXPR) {
|
||||
rhs = rhs.operands()[0];
|
||||
}
|
||||
|
||||
if (DECL_P(rhs) && this.outparams.has(rhs)) {
|
||||
// Copying an outparam pointer. We have to remember this so that
|
||||
// if it is assigned thru later, we pick up the write.
|
||||
state.assignValue(lhs, makeOutparamAV(rhs), isn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cases of this switch that handle something should return from
|
||||
// the function. Anything that does not return is picked up afteward.
|
||||
switch (TREE_CODE(rhs)) {
|
||||
case INTEGER_CST:
|
||||
if (this.outparams.has(lhs)) {
|
||||
warning("assigning to outparam pointer");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case EQ_EXPR: {
|
||||
// We only care about testing outparams for NULL (and then not writing)
|
||||
let [op1, op2] = rhs.operands();
|
||||
if (DECL_P(op1) && this.outparams.has(op1) && expr_literal_int(op2) == 0) {
|
||||
state.update(function(ss) {
|
||||
let [s1, s2] = [ss, ss.copy()]; // s1 true, s2 false
|
||||
s1.assignValue(lhs, av.NONZERO, isn);
|
||||
s1.assignValue(op1, av.NULL, isn);
|
||||
s2.assignValue(lhs, av.ZERO, isn);
|
||||
return [s1, s2];
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CALL_EXPR:
|
||||
/* Embedded CALL_EXPRs are a 4.3 issue */
|
||||
this.processCall(rhs, isn, state, lhs);
|
||||
return;
|
||||
|
||||
case INDIRECT_REF:
|
||||
// If rhs is *outparam and pointer-typed, lhs is NULL iff rhs is
|
||||
// WROTE_NULL. Required for testcase onull.cpp.
|
||||
let v = rhs.operands()[0];
|
||||
if (DECL_P(v) && this.outparams.has(v) &&
|
||||
TREE_CODE(TREE_TYPE(v)) == POINTER_TYPE) {
|
||||
state.update(function(ss) {
|
||||
let val = ss.get(v) == av.WROTE_NULL ? av.ZERO : av.NONZERO;
|
||||
ss.assignValue(lhs, val, isn);
|
||||
return [ ss ];
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing special -- delegate
|
||||
this.zeroNonzero.processAssign(isn, state);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (TREE_CODE(lhs)) {
|
||||
case INDIRECT_REF:
|
||||
// Writing to an outparam. We want to try to figure out if we're
|
||||
// writing NULL.
|
||||
let e = TREE_OPERAND(lhs, 0);
|
||||
if (this.outparams.has(e)) {
|
||||
if (expr_literal_int(rhs) == 0) {
|
||||
state.assignValue(e, av.WROTE_NULL, isn);
|
||||
} else if (DECL_P(rhs)) {
|
||||
state.update(function(ss) {
|
||||
let [s1, s2] = [ss.copy(), ss]; // s1 NULL, s2 non-NULL
|
||||
s1.assignValue(e, av.WROTE_NULL, isn);
|
||||
s1.assignValue(rhs, av.ZERO, isn);
|
||||
s2.assignValue(e, av.WRITTEN, isn);
|
||||
s2.assignValue(rhs, av.NONZERO, isn);
|
||||
return [s1,s2];
|
||||
});
|
||||
} else {
|
||||
state.assignValue(e, av.WRITTEN, isn);
|
||||
}
|
||||
} else {
|
||||
// unsound -- could be writing to anything through this ptr
|
||||
}
|
||||
break;
|
||||
case COMPONENT_REF: // unsound
|
||||
case ARRAY_REF: // unsound
|
||||
case EXC_PTR_EXPR:
|
||||
case FILTER_EXPR:
|
||||
break;
|
||||
default:
|
||||
print(TREE_CODE(lhs));
|
||||
throw new Error("ni");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle an assignment x := test(foo) where test is a simple predicate
|
||||
OutparamCheck.prototype.processTest = function(lhs, call, val, blame, state) {
|
||||
let arg = gimple_call_arg(call, 0);
|
||||
if (DECL_P(arg)) {
|
||||
this.zeroNonzero.predicate(state, lhs, val, arg, blame);
|
||||
} else {
|
||||
state.assignValue(lhs, ESP.TOP, blame);
|
||||
}
|
||||
};
|
||||
|
||||
// The big one: outparam semantics of function calls.
|
||||
OutparamCheck.prototype.processCall = function(call, blame, state, dest) {
|
||||
if (!dest)
|
||||
dest = gimple_call_lhs(call);
|
||||
|
||||
let args = gimple_call_args(call);
|
||||
let callable = callable_arg_function_decl(gimple_call_fn(call));
|
||||
let psem = this.func_param_semantics(callable);
|
||||
|
||||
let name = function_decl_name(callable);
|
||||
if (name == 'NS_FAILED') {
|
||||
this.processTest(dest, call, av.NONZERO, call, state);
|
||||
return;
|
||||
} else if (name == 'NS_SUCCEEDED') {
|
||||
this.processTest(dest, call, av.ZERO, call, state);
|
||||
return;
|
||||
} else if (name == '__builtin_expect') {
|
||||
// Same as an assign from arg 0 to lhs
|
||||
state.assign(dest, args[0], call);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TRACE_CALL_SEM) {
|
||||
print("param semantics:" + psem);
|
||||
}
|
||||
|
||||
if (args.length != psem.length) {
|
||||
let ct = TREE_TYPE(callable);
|
||||
if (TREE_CODE(ct) == POINTER_TYPE) ct = TREE_TYPE(ct);
|
||||
if (args.length < psem.length || !stdarg_p(ct)) {
|
||||
// TODO Can __builtin_memcpy write to an outparam? Probably not.
|
||||
if (name != 'operator new' && name != 'operator delete' &&
|
||||
name != 'operator new []' && name != 'operator delete []' &&
|
||||
name.substr(0, 5) != '__cxa' &&
|
||||
name.substr(0, 9) != '__builtin') {
|
||||
throw Error("bad len for '" + name + "': " + args.length + ' args, ' +
|
||||
psem.length + ' params');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect variables that are possibly written to on callee success
|
||||
let updates = [];
|
||||
for (let i = 0; i < psem.length; ++i) {
|
||||
let arg = args[i];
|
||||
// The arg could be the address of a return-value variable.
|
||||
// This means it's really the nsresult code for the call,
|
||||
// so we treat it the same as the target of an rv assignment.
|
||||
if (TREE_CODE(arg) == ADDR_EXPR) {
|
||||
let v = arg.operands()[0];
|
||||
if (DECL_P(v) && this.retvar_set.has(v)) {
|
||||
dest = v;
|
||||
}
|
||||
}
|
||||
// The arg could be a copy of an outparam. We'll unwrap to the
|
||||
// outparam if it is. The following is cheating a bit because
|
||||
// we munge states together, but it should be OK in practice.
|
||||
arg = unwrap_outparam(arg, state);
|
||||
let sem = psem[i];
|
||||
if (sem == ps.CONST) continue;
|
||||
// At this point, we know the call can write thru this param.
|
||||
// Invalidate any vars whose addresses are passed here. This
|
||||
// is distinct from the rv handling above.
|
||||
if (TREE_CODE(arg) == ADDR_EXPR) {
|
||||
let v = arg.operands()[0];
|
||||
if (DECL_P(v)) {
|
||||
state.remove(v);
|
||||
}
|
||||
}
|
||||
if (!DECL_P(arg) || !this.outparams.has(arg)) continue;
|
||||
// At this point, we may be writing to an outparam
|
||||
updates.push([arg, sem]);
|
||||
}
|
||||
|
||||
if (updates.length) {
|
||||
if (dest != undefined && DECL_P(dest)) {
|
||||
// Update & stored rv. Do updates predicated on success.
|
||||
let [ succ_ret, fail_ret ] = ret_coding(callable);
|
||||
|
||||
state.update(function(ss) {
|
||||
let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail
|
||||
for each (let [vbl, sem] in updates) {
|
||||
s1.assignValue(vbl, sem.val, blame);
|
||||
s1.assignValue(dest, succ_ret, blame);
|
||||
}
|
||||
s2.assignValue(dest, fail_ret, blame);
|
||||
return [s1,s2];
|
||||
});
|
||||
} else {
|
||||
// Discarded rv. Per spec in the bug, we assume that either success
|
||||
// or failure is possible (if not, callee should return void).
|
||||
// Exceptions: Methods that return void and string mutators are
|
||||
// considered no-fail.
|
||||
state.update(function(ss) {
|
||||
for each (let [vbl, sem] in updates) {
|
||||
if (sem == ps.OUTNOFAIL || sem == ps.OUTNOFAILNOCHECK) {
|
||||
ss.assignValue(vbl, av.WRITTEN, blame);
|
||||
return [ss];
|
||||
} else {
|
||||
let [s1, s2] = [ss.copy(), ss]; // s1 success, s2 fail
|
||||
for each (let [vbl, sem] in updates) {
|
||||
s1.assignValue(vbl, sem.val, blame);
|
||||
}
|
||||
return [s1,s2];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// no updates, just kill any destination for the rv
|
||||
if (dest != undefined && DECL_P(dest)) {
|
||||
state.remove(dest, blame);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Return the return value coding of the given function. This is a pair
|
||||
* [ succ, fail ] giving the abstract values of the return value under
|
||||
* success and failure conditions. */
|
||||
function ret_coding(callable) {
|
||||
let type = TREE_TYPE(callable);
|
||||
if (TREE_CODE(type) == POINTER_TYPE) type = TREE_TYPE(type);
|
||||
|
||||
let rtname = TYPE_NAME(TREE_TYPE(type));
|
||||
if (rtname && IDENTIFIER_POINTER(DECL_NAME(rtname)) == 'PRBool') {
|
||||
return [ av.NONZERO, av.ZERO ];
|
||||
} else {
|
||||
return [ av.ZERO, av.NONZERO ];
|
||||
}
|
||||
}
|
||||
|
||||
function unwrap_outparam(arg, state) {
|
||||
if (!DECL_P(arg) || state.factory.outparams.has(arg)) return arg;
|
||||
|
||||
let outparam;
|
||||
for (let ss in state.substates.getValues()) {
|
||||
let val = ss.get(arg);
|
||||
if (val != undefined && val.hasOwnProperty('outparam')) {
|
||||
outparam = val.outparam;
|
||||
}
|
||||
}
|
||||
if (outparam) return outparam;
|
||||
return arg;
|
||||
}
|
||||
|
||||
// Check for errors. Must .run() analysis before calling this.
|
||||
OutparamCheck.prototype.check = function(isvoid, fndecl) {
|
||||
let state = this.cfg.x_exit_block_ptr.stateOut;
|
||||
for (let substate in state.substates.getValues()) {
|
||||
this.checkSubstate(isvoid, fndecl, substate);
|
||||
}
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.checkSubstate = function(isvoid, fndecl, ss) {
|
||||
if (isvoid) {
|
||||
this.checkSubstateSuccess(ss);
|
||||
} else {
|
||||
let [succ, fail] = ret_coding(fndecl);
|
||||
let rv = ss.get(this.retvar);
|
||||
// We want to check if the abstract value of the rv is entirely
|
||||
// contained in the success or failure condition.
|
||||
if (av.meet(rv, succ) == rv) {
|
||||
this.checkSubstateSuccess(ss);
|
||||
} else if (av.meet(rv, fail) == rv) {
|
||||
this.checkSubstateFailure(ss);
|
||||
} else {
|
||||
// This condition indicates a bug in outparams.js. We'll just
|
||||
// warn so we don't break static analysis builds.
|
||||
warning("Outparams checker cannot determine rv success/failure",
|
||||
location_of(fndecl));
|
||||
this.checkSubstateSuccess(ss);
|
||||
this.checkSubstateFailure(ss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @return The return statement in the function
|
||||
* that writes the return value in the given substate.
|
||||
* If the function returns void, then the substate doesn't
|
||||
* matter and we just look for the return. */
|
||||
OutparamCheck.prototype.findReturnStmt = function(ss) {
|
||||
if (this.retvar != undefined)
|
||||
return ss.getBlame(this.retvar);
|
||||
|
||||
if (this.cfg._cached_return)
|
||||
return this.cfg._cached_return;
|
||||
|
||||
for (let bb in cfg_bb_iterator(this.cfg)) {
|
||||
for (let isn in bb_isn_iterator(bb)) {
|
||||
if (isn.tree_code() == GIMPLE_RETURN) {
|
||||
return this.cfg._cached_return = isn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.checkSubstateSuccess = function(ss) {
|
||||
for (let i = 0; i < this.psem_list.length; ++i) {
|
||||
let [v, psem] = [ this.outparam_list[i], this.psem_list[i] ];
|
||||
if (psem == ps.INOUT) continue;
|
||||
let val = ss.get(v);
|
||||
if (val == av.NOT_WRITTEN) {
|
||||
this.logResult('succ', 'not_written', 'error');
|
||||
this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"],
|
||||
[v, "outparam declared here"]);
|
||||
} else if (val == av.MAYBE_WRITTEN) {
|
||||
this.logResult('succ', 'maybe_written', 'error');
|
||||
|
||||
let blameStmt = ss.getBlame(v);
|
||||
let callMsg;
|
||||
let callName = "";
|
||||
try {
|
||||
let call = TREE_CHECK(blameStmt, GIMPLE_CALL, GIMPLE_MODIFY_STMT);
|
||||
let callDecl = callable_arg_function_decl(gimple_call_fn(call));
|
||||
|
||||
callMsg = [callDecl, "declared here"];
|
||||
callName = " '" + decl_name(callDecl) + "'";
|
||||
}
|
||||
catch (e if e.TreeCheckError) { }
|
||||
|
||||
this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' not written on NS_SUCCEEDED(return value)"],
|
||||
[v, "outparam declared here"],
|
||||
[blameStmt, "possibly written by unannotated function call" + callName],
|
||||
callMsg);
|
||||
} else {
|
||||
this.logResult('succ', '', 'ok');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.checkSubstateFailure = function(ss) {
|
||||
for (let i = 0; i < this.psem_list.length; ++i) {
|
||||
let [v, ps] = [ this.outparam_list[i], this.psem_list[i] ];
|
||||
let val = ss.get(v);
|
||||
if (val == av.WRITTEN) {
|
||||
this.logResult('fail', 'written', 'error');
|
||||
if (WARN_ON_SET_FAILURE) {
|
||||
this.warn([this.findReturnStmt(ss), "outparam '" + expr_display(v) + "' written on NS_FAILED(return value)"],
|
||||
[v, "outparam declared here"],
|
||||
[ss.getBlame(v), "written here"]);
|
||||
}
|
||||
} else if (val == av.WROTE_NULL) {
|
||||
this.logResult('fail', 'wrote_null', 'warning');
|
||||
if (WARN_ON_SET_NULL) {
|
||||
this.warn([this.findReturnStmt(ss), "NULL written to outparam '" + expr_display(v) + "' on NS_FAILED(return value)"],
|
||||
[v, "outparam declared here"],
|
||||
[ss.getBlame(v), "written here"]);
|
||||
}
|
||||
} else {
|
||||
this.logResult('fail', '', 'ok');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a warning from one or more tuples [treeforloc, message]
|
||||
*/
|
||||
OutparamCheck.prototype.warn = function(arg0) {
|
||||
let loc = safe_location_of(arg0[0]);
|
||||
let msg = arg0[1];
|
||||
|
||||
for (let i = 1; i < arguments.length; ++i) {
|
||||
if (arguments[i] === undefined) continue;
|
||||
let [atree, amsg] = arguments[i];
|
||||
msg += "\n" + loc_string(safe_location_of(atree)) + ": " + amsg;
|
||||
}
|
||||
warning(msg, loc);
|
||||
}
|
||||
|
||||
OutparamCheck.prototype.logResult = function(rv, msg, kind) {
|
||||
if (LOG_RESULTS) {
|
||||
let s = [ '"' + x + '"' for each (x in [ loc_string(location_of(this.fndecl)), function_decl_name(this.fndecl), rv, msg, kind ]) ].join(', ');
|
||||
print(":LR: (" + s + ")");
|
||||
}
|
||||
}
|
||||
|
||||
// Parameter Semantics values -- indicates whether a parameter is
|
||||
// an outparam.
|
||||
// label Used for debugging output
|
||||
// val Abstract value (state) that holds on an argument after
|
||||
// a call
|
||||
// check True if parameters with this semantics should be
|
||||
// checked by this analysis
|
||||
let ps = {
|
||||
OUTNOFAIL: { label: 'out-no-fail', val: av.WRITTEN, check: true },
|
||||
// Special value for receiver of strings methods. Callers should
|
||||
// consider this to be an outparam (i.e., it modifies the string),
|
||||
// but we don't want to check the method itself.
|
||||
OUTNOFAILNOCHECK: { label: 'out-no-fail-no-check' },
|
||||
OUT: { label: 'out', val: av.WRITTEN, check: true },
|
||||
INOUT: { label: 'inout', val: av.WRITTEN, check: true },
|
||||
MAYBE: { label: 'maybe', val: av.MAYBE_WRITTEN}, // maybe out
|
||||
CONST: { label: 'const' } // i.e. not out
|
||||
};
|
||||
|
||||
// Return the param semantics of a FUNCTION_DECL or VAR_DECL representing
|
||||
// a function pointer. The result is a pair [ ann, sems ].
|
||||
OutparamCheck.prototype.func_param_semantics = function(callable) {
|
||||
let ftype = TREE_TYPE(callable);
|
||||
if (TREE_CODE(ftype) == POINTER_TYPE) ftype = TREE_TYPE(ftype);
|
||||
// What failure semantics to use for outparams
|
||||
let rtype = TREE_TYPE(ftype);
|
||||
let nofail = TREE_CODE(rtype) == VOID_TYPE;
|
||||
// Whether to guess outparams by type
|
||||
let guess = type_string(rtype) == 'nsresult';
|
||||
|
||||
// Set up param lists for analysis
|
||||
let params; // param decls, if available
|
||||
let types; // param types
|
||||
let string_mutator = false;
|
||||
if (TREE_CODE(callable) == FUNCTION_DECL) {
|
||||
params = [ p for (p in function_decl_params(callable)) ];
|
||||
types = [ TREE_TYPE(p) for each (p in params) ];
|
||||
string_mutator = is_string_mutator(callable);
|
||||
} else {
|
||||
types = [ p for (p in function_type_args(ftype))
|
||||
if (TREE_CODE(p) != VOID_TYPE) ];
|
||||
}
|
||||
|
||||
// Analyze params
|
||||
let ans = [];
|
||||
for (let i = 0; i < types.length; ++i) {
|
||||
let sem;
|
||||
if (i == 0 && string_mutator) {
|
||||
// Special case: string mutator receiver is an no-fail outparams
|
||||
// but not checkable
|
||||
sem = ps.OUTNOFAILNOCHECK;
|
||||
} else {
|
||||
if (params) sem = decode_attr(DECL_ATTRIBUTES(params[i]));
|
||||
if (TRACE_CALL_SEM >= 2) print("param " + i + ": annotated " + sem);
|
||||
if (sem == undefined) {
|
||||
sem = decode_attr(TYPE_ATTRIBUTES(types[i]));
|
||||
if (TRACE_CALL_SEM >= 2) print("type " + i + ": annotated " + sem);
|
||||
if (sem == undefined) {
|
||||
if (guess && type_is_outparam(types[i])) {
|
||||
// Params other than last are guessed as MAYBE
|
||||
sem = i < types.length - 1 ? ps.MAYBE : ps.OUT;
|
||||
} else {
|
||||
sem = ps.CONST;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sem == ps.OUT && nofail) sem = ps.OUTNOFAIL;
|
||||
}
|
||||
if (sem == undefined) throw new Error("assert");
|
||||
ans.push(sem);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
/* Decode parameter semantics GCC attributes.
|
||||
* @param attrs GCC attributes of a parameter. E.g., TYPE_ATTRIBUTES
|
||||
* or DECL_ATTRIBUTES of an item
|
||||
* @return The parameter semantics value defined by the attributes,
|
||||
* or undefined if no such attributes were present. */
|
||||
function decode_attr(attrs) {
|
||||
// Note: we're not checking for conflicts, we just take the first
|
||||
// one we find.
|
||||
for each (let attr in rectify_attributes(attrs)) {
|
||||
if (attr.name == 'user') {
|
||||
for each (let arg in attr.args) {
|
||||
if (arg == 'NS_outparam') {
|
||||
return ps.OUT;
|
||||
} else if (arg == 'NS_inoutparam') {
|
||||
return ps.INOUT;
|
||||
} else if (arg == 'NS_inparam') {
|
||||
return ps.CONST;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/* @return true if the given type appears to be an outparam
|
||||
* type based on the type alone (i.e., not considering
|
||||
* attributes. */
|
||||
function type_is_outparam(type) {
|
||||
switch (TREE_CODE(type)) {
|
||||
case POINTER_TYPE:
|
||||
return pointer_type_is_outparam(TREE_TYPE(type));
|
||||
case REFERENCE_TYPE:
|
||||
let rt = TREE_TYPE(type);
|
||||
return !TYPE_READONLY(rt) && is_string_type(rt);
|
||||
default:
|
||||
// Note: This is unsound for UNION_TYPE, because the union could
|
||||
// contain a pointer.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper for type_is_outparam.
|
||||
* @return true if 'pt *' looks like an outparam type. */
|
||||
function pointer_type_is_outparam(pt) {
|
||||
if (TYPE_READONLY(pt)) return false;
|
||||
|
||||
switch (TREE_CODE(pt)) {
|
||||
case POINTER_TYPE:
|
||||
case ARRAY_TYPE: {
|
||||
// Look for void **, nsIFoo **, char **, PRUnichar **
|
||||
let ppt = TREE_TYPE(pt);
|
||||
let tname = TYPE_NAME(ppt);
|
||||
if (tname == undefined) return false;
|
||||
let name = decl_name_string(tname);
|
||||
return name == 'void' || name == 'char' || name == 'PRUnichar' ||
|
||||
name.substr(0, 3) == 'nsI';
|
||||
}
|
||||
case INTEGER_TYPE: {
|
||||
// char * and PRUnichar * are probably strings, otherwise guess
|
||||
// it is an integer outparam.
|
||||
let name = decl_name_string(TYPE_NAME(pt));
|
||||
return name != 'char' && name != 'PRUnichar';
|
||||
}
|
||||
case ENUMERAL_TYPE:
|
||||
case REAL_TYPE:
|
||||
case UNION_TYPE:
|
||||
case BOOLEAN_TYPE:
|
||||
return true;
|
||||
case RECORD_TYPE:
|
||||
// TODO: should we consider field writes?
|
||||
return false;
|
||||
case FUNCTION_TYPE:
|
||||
case VOID_TYPE:
|
||||
return false;
|
||||
default:
|
||||
throw new Error("can't guess if a pointer to this type is an outparam: " +
|
||||
TREE_CODE(pt) + ': ' + type_string(pt));
|
||||
}
|
||||
}
|
||||
|
||||
// Map type name to boolean as to whether it is a string.
|
||||
let cached_string_types = MapFactory.create_map(
|
||||
function (x, y) x == y,
|
||||
function (x) x,
|
||||
function (t) t,
|
||||
function (t) t);
|
||||
|
||||
// Base string types. Others will be found by searching the inheritance
|
||||
// graph.
|
||||
|
||||
cached_string_types.put('nsAString', true);
|
||||
cached_string_types.put('nsACString', true);
|
||||
cached_string_types.put('nsAString_internal', true);
|
||||
cached_string_types.put('nsACString_internal', true);
|
||||
|
||||
// Return true if the given type represents a Mozilla string type.
|
||||
// The binfo arg is the binfo to use for further iteration. This is
|
||||
// for internal use only, users of this function should pass only
|
||||
// one arg.
|
||||
function is_string_type(type, binfo) {
|
||||
if (TREE_CODE(type) != RECORD_TYPE) return false;
|
||||
//print(">>>IST " + type_string(type));
|
||||
let name = decl_name_string(TYPE_NAME(type));
|
||||
let ans = cached_string_types.get(name);
|
||||
if (ans != undefined) return ans;
|
||||
|
||||
ans = false;
|
||||
binfo = binfo != undefined ? binfo : TYPE_BINFO(type);
|
||||
if (binfo != undefined) {
|
||||
for each (let base in VEC_iterate(BINFO_BASE_BINFOS(binfo))) {
|
||||
let parent_ans = is_string_type(BINFO_TYPE(base), base);
|
||||
if (parent_ans) {
|
||||
ans = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
cached_string_types.put(name, ans);
|
||||
//print("<<<IST " + type_string(type) + ' ' + ans);
|
||||
return ans;
|
||||
}
|
||||
|
||||
function is_string_ptr_type(type) {
|
||||
return TREE_CODE(type) == POINTER_TYPE && is_string_type(TREE_TYPE(type));
|
||||
}
|
||||
|
||||
// Return true if the given function is a mutator method of a Mozilla
|
||||
// string type.
|
||||
function is_string_mutator(fndecl) {
|
||||
let first_param = function() {
|
||||
for (let p in function_decl_params(fndecl)) {
|
||||
return p;
|
||||
}
|
||||
return undefined;
|
||||
}();
|
||||
|
||||
return first_param != undefined &&
|
||||
decl_name_string(first_param) == 'this' &&
|
||||
is_string_ptr_type(TREE_TYPE(first_param)) &&
|
||||
!TYPE_READONLY(TREE_TYPE(TREE_TYPE(first_param)));
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
require({ after_gcc_pass: "cfg" });
|
||||
|
||||
include("gcc_util.js");
|
||||
include("unstable/lazy_types.js");
|
||||
|
||||
function process_type(c)
|
||||
{
|
||||
if ((c.kind == 'class' || c.kind == 'struct') &&
|
||||
!c.isIncomplete)
|
||||
isStack(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* A BlameChain records a chain of one or more location/message pairs. It
|
||||
* can be used to issue a complex error message such as:
|
||||
* location: error: Allocated class Foo on the heap
|
||||
* locationofFoo: class Foo inherits from class Bar
|
||||
* locationofBar: in class Bar
|
||||
* locationofBarMem: Member Bar::mFoo
|
||||
* locationofBaz: class Baz is annotated NS_STACK
|
||||
*/
|
||||
function BlameChain(loc, message, prev)
|
||||
{
|
||||
this.loc = loc;
|
||||
this.message = message;
|
||||
this.prev = prev;
|
||||
}
|
||||
|
||||
BlameChain.prototype.toString = function()
|
||||
{
|
||||
let loc = this.loc;
|
||||
if (loc === undefined)
|
||||
loc = "<unknown location>";
|
||||
|
||||
let str = '%s: %s'.format(loc.toString(), this.message);
|
||||
if (this.prev)
|
||||
str += "\n%s".format(this.prev);
|
||||
return str;
|
||||
};
|
||||
|
||||
function isStack(c)
|
||||
{
|
||||
function calculate()
|
||||
{
|
||||
if (hasAttribute(c, 'NS_stack'))
|
||||
return new BlameChain(c.loc, '%s %s is annotated NS_STACK_CLASS'.format(c.kind, c.name));
|
||||
|
||||
for each (let base in c.bases) {
|
||||
let r = isStack(base.type);
|
||||
if (r != null)
|
||||
return new BlameChain(c.loc, '%s %s is a base of %s %s'.format(base.type.kind, base.type.name, c.kind, c.name), r);
|
||||
}
|
||||
|
||||
for each (let member in c.members) {
|
||||
if (member.isFunction)
|
||||
continue;
|
||||
|
||||
if (hasAttribute(member, 'NS_okonheap'))
|
||||
continue;
|
||||
|
||||
let type = member.type;
|
||||
while (true) {
|
||||
if (type === undefined)
|
||||
break;
|
||||
|
||||
if (type.isArray) {
|
||||
type = type.type;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type.typedef) {
|
||||
if (hasAttribute(type, 'NS_stack'))
|
||||
return true;
|
||||
|
||||
type = type.typedef;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (type === undefined) {
|
||||
warning("incomplete type for member " + member + ".", member.loc);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type.isPointer || type.isReference)
|
||||
continue;
|
||||
|
||||
if (!type.kind || (type.kind != 'class' && type.kind != 'struct'))
|
||||
continue;
|
||||
|
||||
let r = isStack(type);
|
||||
if (r != null)
|
||||
return new BlameChain(c.loc, 'In class %s'.format(c.name),
|
||||
new BlameChain(member.loc, 'Member %s'.format(member.name), r));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!c.hasOwnProperty('isStack'))
|
||||
c.isStack = calculate();
|
||||
|
||||
return c.isStack;
|
||||
}
|
||||
|
||||
function process_tree(fn)
|
||||
{
|
||||
if (hasAttribute(dehydra_convert(fn), 'NS_suppress_stackcheck'))
|
||||
return;
|
||||
|
||||
let cfg = function_decl_cfg(fn);
|
||||
|
||||
for (let bb in cfg_bb_iterator(cfg)) {
|
||||
let it = bb_isn_iterator(bb);
|
||||
for (let isn in it) {
|
||||
if (isn.tree_code() != GIMPLE_CALL)
|
||||
continue;
|
||||
|
||||
let name = gimple_call_function_name(isn);
|
||||
if (name != "operator new" && name != "operator new []")
|
||||
continue;
|
||||
|
||||
// ignore placement new
|
||||
// TODO? ensure 2nd arg is local stack variable
|
||||
if (gimple_call_num_args(isn) == 2 &&
|
||||
TREE_TYPE(gimple_call_arg(isn, 1)).tree_code() == POINTER_TYPE)
|
||||
continue;
|
||||
|
||||
let newLhs = gimple_call_lhs(isn);
|
||||
if (!newLhs)
|
||||
error("Non assigning call to operator new", location_of(isn));
|
||||
|
||||
// if isn is the last of its block there are other problems...
|
||||
assign = it.next();
|
||||
|
||||
// Calls to |new| are always followed by an assignment, casting the void ptr to which
|
||||
// |new| was assigned, to a ptr variable of the same type as the allocated object.
|
||||
// Exception: explicit calls to |::operator new (size_t)|, which can be ignored.
|
||||
if (assign.tree_code() != GIMPLE_ASSIGN)
|
||||
continue;
|
||||
|
||||
let assignRhs = gimple_op(assign, 1);
|
||||
if (newLhs != assignRhs)
|
||||
continue;
|
||||
|
||||
let assignLhs = gimple_op(assign, 0);
|
||||
let type = TREE_TYPE(TREE_TYPE(assignLhs));
|
||||
let dehydraType = dehydra_convert(type);
|
||||
|
||||
let r = isStack(dehydraType);
|
||||
if (r)
|
||||
warning("constructed object of type '%s' not on the stack: %s".format(dehydraType.name, r), location_of(isn));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Detects static initializers i.e. functions called during static initialization.
|
||||
*/
|
||||
|
||||
require({ after_gcc_pass: "cfg" });
|
||||
|
||||
function process_tree(fn) {
|
||||
for each (let attr in translate_attributes(DECL_ATTRIBUTES(fn)))
|
||||
if (attr.name == "constructor")
|
||||
warning(pretty_func(fn) + " marked with constructor attribute\n");
|
||||
|
||||
if (decl_name_string(fn) != "__static_initialization_and_destruction_0")
|
||||
return;
|
||||
|
||||
let cfg = function_decl_cfg(fn);
|
||||
for (let isn in cfg_isn_iterator(cfg)) {
|
||||
if (isn.tree_code() != GIMPLE_CALL)
|
||||
continue;
|
||||
let decl = gimple_call_fndecl(isn);
|
||||
let lhs = gimple_call_lhs(isn);
|
||||
if (lhs) {
|
||||
warning(pretty_var(lhs) + " defined by call to " + pretty_func(decl) +
|
||||
" during static initialization", location_of(lhs));
|
||||
} else {
|
||||
let arg = constructorArg(isn);
|
||||
if (arg)
|
||||
warning(pretty_var(arg) + " defined by call to constructor " + pretty_func(decl) +
|
||||
" during static initialization", location_of(arg));
|
||||
else
|
||||
warning(pretty_func(decl) + " called during static initialization", location_of(decl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function constructorArg(call) {
|
||||
let decl = gimple_call_fndecl(call);
|
||||
|
||||
if (!DECL_CONSTRUCTOR_P(decl))
|
||||
return null;
|
||||
|
||||
let arg = gimple_call_arg_iterator(call).next();
|
||||
if (TYPE_MAIN_VARIANT(TREE_TYPE(TREE_TYPE(arg))) != DECL_CONTEXT(decl))
|
||||
throw new Error("malformed constructor call?!");
|
||||
|
||||
return arg.tree_code() == ADDR_EXPR ? TREE_OPERAND(arg, 0) : arg;
|
||||
}
|
||||
|
||||
function pretty_func(fn) {
|
||||
return rfunc_string(rectify_function_decl(fn));
|
||||
}
|
||||
|
||||
function pretty_var(v) {
|
||||
return type_string(TREE_TYPE(v)) + " " + expr_display(v);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsString.h"
|
||||
|
||||
/* do nothing else */
|
|
@ -1,402 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let dumpTypes = options['dump-types'].split(',');
|
||||
|
||||
let interestingList = {};
|
||||
let typelist = {};
|
||||
|
||||
function interestingType(t)
|
||||
{
|
||||
let name = t.name;
|
||||
|
||||
if (dumpTypes.some(function(n) n == name)) {
|
||||
interestingList[name] = t;
|
||||
typelist[name] = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
for each (let base in t.bases) {
|
||||
if (base.access == 'public' && interestingType(base.type)) {
|
||||
typelist[name] = t;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function addSubtype(t, subt)
|
||||
{
|
||||
if (subt.typedef === undefined &&
|
||||
subt.kind === undefined)
|
||||
throw Error("Unexpected subtype: not class or typedef: " + subt);
|
||||
|
||||
if (t.subtypes === undefined)
|
||||
t.subtypes = [];
|
||||
|
||||
t.subtypes.push(subt);
|
||||
}
|
||||
|
||||
function process_type(t)
|
||||
{
|
||||
interestingType(t);
|
||||
|
||||
for each (let base in t.bases)
|
||||
addSubtype(base.type, t);
|
||||
}
|
||||
|
||||
function process_decl(d)
|
||||
{
|
||||
if (d.typedef !== undefined && d.memberOf)
|
||||
addSubtype(d.memberOf, d);
|
||||
}
|
||||
|
||||
function publicBases(t)
|
||||
{
|
||||
yield t;
|
||||
|
||||
for each (let base in t.bases)
|
||||
if (base.access == "public")
|
||||
for each (let gbase in publicBases(base.type))
|
||||
yield gbase;
|
||||
}
|
||||
|
||||
function publicMembers(t)
|
||||
{
|
||||
for each (let base in publicBases(t)) {
|
||||
for each (let member in base.members) {
|
||||
if (member.access === undefined)
|
||||
throw Error("Harumph: member without access? " + member);
|
||||
|
||||
if (member.access != "public")
|
||||
continue;
|
||||
|
||||
yield member;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the short name of a decl name. E.g. turn
|
||||
* "MyNamespace::MyClass::Method(int j) const" into
|
||||
* "Method"
|
||||
*/
|
||||
function getShortName(decl)
|
||||
{
|
||||
let name = decl.name;
|
||||
let lp = name.lastIndexOf('(');
|
||||
if (lp != -1)
|
||||
name = name.slice(0, lp);
|
||||
|
||||
lp = name.lastIndexOf('::');
|
||||
if (lp != -1)
|
||||
name = name.slice(lp + 2);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove functions in a base class which were overridden in a derived
|
||||
* class.
|
||||
*
|
||||
* Although really, we should perhaps do this the other way around, or even
|
||||
* group the two together, but that can come later.
|
||||
*/
|
||||
function removeOverrides(members)
|
||||
{
|
||||
let overrideMap = {};
|
||||
for (let i = members.length - 1; i >= 0; --i) {
|
||||
let m = members[i];
|
||||
if (!m.isFunction)
|
||||
continue;
|
||||
|
||||
let shortName = getShortName(m);
|
||||
|
||||
let overrides = overrideMap[shortName];
|
||||
if (overrides === undefined) {
|
||||
overrideMap[shortName] = [m];
|
||||
continue;
|
||||
}
|
||||
|
||||
let found = false;
|
||||
for each (let override in overrides) {
|
||||
if (signaturesMatch(override, m)) {
|
||||
// remove members[i], it was overridden
|
||||
members.splice(i, 1);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
overrides.push(m);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the starting position of lines within a file.
|
||||
*/
|
||||
function getLineLocations(fdata)
|
||||
{
|
||||
yield 0;
|
||||
|
||||
let r = /\n/y;
|
||||
let pos = 0;
|
||||
let i = 1;
|
||||
for (;;) {
|
||||
pos = fdata.indexOf('\n', pos) + 1;
|
||||
if (pos == 0)
|
||||
break;
|
||||
|
||||
yield pos;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return the doxygen comment immediately prior to the location
|
||||
* object that was passed in.
|
||||
*
|
||||
* @todo: parse doccomment data such as @param, @returns
|
||||
* @todo: parse comments for markup
|
||||
*/
|
||||
function getDocComment(loc)
|
||||
{
|
||||
let fdata = read_file(loc.file);
|
||||
let linemap = [l for (l in getLineLocations(fdata))];
|
||||
|
||||
if (loc.line >= linemap.length) {
|
||||
warning("Location larger than actual header: " + loc);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
let endpos = linemap[loc.line - 1] + loc.column - 1;
|
||||
let semipos = fdata.lastIndexOf(';', endpos);
|
||||
let bracepos = fdata.lastIndexOf('}', endpos);
|
||||
let searchslice = fdata.slice(Math.max(semipos, bracepos) + 1, endpos);
|
||||
|
||||
let m = searchslice.match(/\/\*\*[\s\S]*?\*\//gm);
|
||||
if (m === null)
|
||||
return <></>;
|
||||
|
||||
let dc = m[m.length - 1].slice(3, -2);
|
||||
dc = dc.replace(/^\s*(\*+[ \t]*)?/gm, "");
|
||||
|
||||
return <pre class="doccomment">{dc}</pre>;
|
||||
}
|
||||
|
||||
function typeName(t)
|
||||
{
|
||||
if (t.name !== undefined)
|
||||
return t.name;
|
||||
|
||||
if (t.isPointer)
|
||||
return "%s%s*".format(t.isConst ? "const " : "", typeName(t.type));
|
||||
|
||||
if (t.isReference)
|
||||
return "%s%s&".format(t.isConst ? "const " : "", typeName(t.type));
|
||||
|
||||
return t.toString();
|
||||
}
|
||||
|
||||
function publicBaseList(t)
|
||||
{
|
||||
let l = <ul/>;
|
||||
for each (let b in t.bases) {
|
||||
if (b.access == 'public')
|
||||
l.* += <li><a href={"/en/%s".format(b.type.name)}>{b.type.name}</a></li>;
|
||||
}
|
||||
|
||||
if (l.*.length() == 0)
|
||||
return <></>;
|
||||
|
||||
return <>
|
||||
<h2>Base Classes</h2>
|
||||
{l}
|
||||
</>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a source-link for a given location.
|
||||
*/
|
||||
function getLocLink(loc, text)
|
||||
{
|
||||
return <a class="loc"
|
||||
href={"http://hg.mozilla.org/mozilla-central/file/%s/[LOC%s]#l%i".format(options.rev, loc.file, loc.line)}>{text}</a>;
|
||||
}
|
||||
|
||||
function dumpType(t)
|
||||
{
|
||||
print("DUMP-TYPE(%s)".format(t.name));
|
||||
|
||||
let methodOverview = <tbody />;
|
||||
let methodList = <div/>;
|
||||
let memberList = <></>;
|
||||
|
||||
let shortNameMap = {};
|
||||
|
||||
let members = [m for (m in publicMembers(t))];
|
||||
|
||||
removeOverrides(members);
|
||||
|
||||
for each (let m in members) {
|
||||
let qname = m.memberOf.name + '::';
|
||||
|
||||
// we don't inherit constructors from base classes
|
||||
if (m.isConstructor && m.memberOf !== t)
|
||||
continue;
|
||||
|
||||
if (m.name.indexOf(qname) != 0)
|
||||
throw Error("Member name not qualified?");
|
||||
|
||||
let name = m.name.slice(qname.length);
|
||||
|
||||
if (name.indexOf('~') == 0)
|
||||
continue;
|
||||
|
||||
if (m.isFunction) {
|
||||
let innerList;
|
||||
|
||||
let shortName = getShortName(m);
|
||||
if (m.isConstructor)
|
||||
shortName = 'Constructors';
|
||||
|
||||
if (shortNameMap.hasOwnProperty(shortName)) {
|
||||
innerList = shortNameMap[shortName];
|
||||
}
|
||||
else {
|
||||
let overview =
|
||||
<tr><td>
|
||||
<a href={'#%s'.format(escape(shortName))}>{shortName}</a>
|
||||
</td></tr>;
|
||||
|
||||
if (m.isConstructor)
|
||||
methodOverview.insertChildAfter(null, overview);
|
||||
else
|
||||
methodOverview.appendChild(overview);
|
||||
|
||||
let shortMarkup =
|
||||
<div>
|
||||
<h3 id={shortName}>{shortName}</h3>
|
||||
<dl/>
|
||||
</div>;
|
||||
|
||||
|
||||
if (m.isConstructor)
|
||||
methodList.insertChildAfter(null, shortMarkup);
|
||||
else
|
||||
methodList.appendChild(shortMarkup);
|
||||
|
||||
innerList = shortMarkup.dl;
|
||||
shortNameMap[shortName] = innerList;
|
||||
}
|
||||
|
||||
let parameters = <ul/>;
|
||||
for each (p in m.parameters) {
|
||||
let name = p.name;
|
||||
if (name == 'this')
|
||||
continue;
|
||||
|
||||
if (/^D_\d+$/.test(name))
|
||||
name = '<anonymous>';
|
||||
|
||||
parameters.* += <li>{typeName(p.type)} {name}</li>;
|
||||
}
|
||||
|
||||
innerList.* +=
|
||||
<>
|
||||
<dt id={name} class="methodName">
|
||||
<code>{typeName(m.type.type)} {name}</code> - {getLocLink(m.loc, "source")}
|
||||
</dt>
|
||||
<dd>
|
||||
{getDocComment(m.loc)}
|
||||
{parameters.*.length() > 0 ?
|
||||
<>
|
||||
<h4>Parameters</h4>
|
||||
{parameters}
|
||||
</> : <></>}
|
||||
</dd>
|
||||
</>;
|
||||
}
|
||||
else {
|
||||
memberList += <li class="member">{name}</li>;
|
||||
}
|
||||
}
|
||||
|
||||
let r =
|
||||
<body>
|
||||
<p>{getLocLink(t.loc, "Class Declaration")}</p>
|
||||
|
||||
{getDocComment(t.loc)}
|
||||
|
||||
{dumpTypes.some(function(n) n == t.name) ?
|
||||
<>
|
||||
[MAP{t.name}-graph.map]
|
||||
<img src={"/@api/deki/pages/=en%%252F%s/files/=%s-graph.png".format(t.name, t.name)} usemap="#classes" />
|
||||
</> : <></>
|
||||
}
|
||||
|
||||
{methodOverview.*.length() > 0 ?
|
||||
<>
|
||||
<h2>Method Overview</h2>
|
||||
<table class="standard-table">{methodOverview}</table>
|
||||
</> :
|
||||
""
|
||||
}
|
||||
|
||||
{publicBaseList(t)}
|
||||
|
||||
<h2>Data Members</h2>
|
||||
|
||||
{memberList.*.length() > 0 ?
|
||||
memberList :
|
||||
<p><em>No public members.</em></p>
|
||||
}
|
||||
|
||||
<h2>Methods</h2>
|
||||
|
||||
{methodList.*.length() > 0 ?
|
||||
methodList :
|
||||
<p><em>No public methods.</em></p>
|
||||
}
|
||||
|
||||
</body>;
|
||||
|
||||
write_file(t.name + ".html", r.toXMLString());
|
||||
}
|
||||
|
||||
function graphType(t)
|
||||
{
|
||||
print("GRAPH-TYPE(%s)".format(t.name));
|
||||
|
||||
let contents = "digraph classes {\n"
|
||||
+ " node [shape=rectangle fontsize=11]\n"
|
||||
+ " %s;\n".format(t.name);
|
||||
|
||||
function graphClass(c)
|
||||
{
|
||||
contents += '%s [URL="http://developer.mozilla.org/en/%s"]\n'.format(c.name, c.name);
|
||||
|
||||
for each (let st in c.subtypes) {
|
||||
contents += " %s -> %s;\n".format(c.name, st.name);
|
||||
graphClass(st);
|
||||
}
|
||||
}
|
||||
|
||||
graphClass(t);
|
||||
|
||||
contents += "}\n";
|
||||
|
||||
write_file(t.name + "-graph.gv", contents);
|
||||
}
|
||||
|
||||
function input_end()
|
||||
{
|
||||
for (let p in typelist)
|
||||
dumpType(typelist[p]);
|
||||
|
||||
for (let n in interestingList)
|
||||
graphType(interestingList[n]);
|
||||
}
|
|
@ -38,9 +38,6 @@ TEST_TOOL_DIRS += [
|
|||
# 'reflect/xptcall/tests,
|
||||
#]
|
||||
|
||||
if CONFIG['DEHYDRA_PATH']:
|
||||
DIRS += ['analysis']
|
||||
|
||||
CONFIGURE_DEFINE_FILES += [
|
||||
'xpcom-config.h',
|
||||
'xpcom-private.h',
|
||||
|
|
Загрузка…
Ссылка в новой задаче