mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 03:05:34 +00:00
Bug 1259850 - Rewrite the test suite, add several tests, r=terrence
MozReview-Commit-ID: HtJ0uA3IfXZ --HG-- rename : js/src/devtools/rootAnalysis/run-test.py => js/src/devtools/rootAnalysis/t/sixgill.py extra : rebase_source : ae42485def39c26798bee72a2544034d1a557d3c
This commit is contained in:
parent
44e27262d8
commit
cfed81ff96
@ -3,150 +3,84 @@
|
||||
# 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/.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import site
|
||||
import subprocess
|
||||
import argparse
|
||||
|
||||
testdir = os.path.abspath(os.path.dirname(__file__))
|
||||
testdir = os.path.abspath(os.path.join(os.path.dirname(__file__), 't'))
|
||||
site.addsitedir(testdir)
|
||||
from testlib import Test, equal
|
||||
|
||||
cfg = {}
|
||||
cfg['SIXGILL_ROOT'] = os.environ.get('SIXGILL',
|
||||
os.path.join(testdir, "sixgill"))
|
||||
cfg['SIXGILL_BIN'] = os.environ.get('SIXGILL_BIN',
|
||||
os.path.join(cfg['SIXGILL_ROOT'], "usr", "bin"))
|
||||
cfg['SIXGILL_PLUGIN'] = os.environ.get('SIXGILL_PLUGIN',
|
||||
os.path.join(cfg['SIXGILL_ROOT'], "usr", "libexec", "sixgill", "gcc", "xgill.so"))
|
||||
cfg['CC'] = os.environ.get("CC",
|
||||
"gcc")
|
||||
cfg['CXX'] = os.environ.get("CXX",
|
||||
cfg.get('CC', 'g++'))
|
||||
cfg['JS_BIN'] = os.environ["JS"]
|
||||
scriptdir = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
parser = argparse.ArgumentParser(description='run hazard analysis tests')
|
||||
parser.add_argument(
|
||||
'--js', default=os.environ.get('JS'),
|
||||
help='JS binary to run the tests with')
|
||||
parser.add_argument(
|
||||
'--sixgill', default=os.environ.get('SIXGILL', os.path.join(testdir, "sixgill")),
|
||||
help='Path to root of sixgill installation')
|
||||
parser.add_argument(
|
||||
'--sixgill-bin', default=os.environ.get('SIXGILL_BIN'),
|
||||
help='Path to sixgill binary dir')
|
||||
parser.add_argument(
|
||||
'--sixgill-plugin', default=os.environ.get('SIXGILL_PLUGIN'),
|
||||
help='Full path to sixgill gcc plugin')
|
||||
parser.add_argument(
|
||||
'--gccdir', default=os.environ.get('GCCDIR'),
|
||||
help='Path to GCC installation dir')
|
||||
parser.add_argument(
|
||||
'--cc', default=os.environ.get('CC'),
|
||||
help='Path to gcc')
|
||||
parser.add_argument(
|
||||
'--cxx', default=os.environ.get('CXX'),
|
||||
help='Path to g++')
|
||||
parser.add_argument(
|
||||
'--verbose', '-v', action='store_true',
|
||||
help='Display verbose output, including commands executed')
|
||||
|
||||
cfg = parser.parse_args()
|
||||
|
||||
if not cfg.js:
|
||||
exit('Must specify JS binary through environment variable or --js option')
|
||||
if not cfg.cc:
|
||||
if cfg.gccdir:
|
||||
cfg.cc = os.path.join(cfg.gccdir, "bin", "gcc")
|
||||
else:
|
||||
cfg.cc = "gcc"
|
||||
if not cfg.cxx:
|
||||
if cfg.gccdir:
|
||||
cfg.cxx = os.path.join(cfg.gccdir, "bin", "g++")
|
||||
else:
|
||||
cfg.cxx = "g++"
|
||||
if not cfg.sixgill_bin:
|
||||
cfg.sixgill_bin = os.path.join(cfg.sixgill, "usr", "bin")
|
||||
if not cfg.sixgill_plugin:
|
||||
cfg.sixgill_plugin = os.path.join(cfg.sixgill, "usr", "libexec", "sixgill", "gcc", "xgill.so")
|
||||
|
||||
subprocess.check_call([cfg.js, '-e', 'if (!getBuildConfiguration()["has-ctypes"]) quit(1)'])
|
||||
|
||||
def binpath(prog):
|
||||
return os.path.join(cfg['SIXGILL_BIN'], prog)
|
||||
return os.path.join(cfg.sixgill_bin, prog)
|
||||
|
||||
if not os.path.exists("test-output"):
|
||||
os.mkdir("test-output")
|
||||
try:
|
||||
os.mkdir(os.path.join('t', 'out'))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Simplified version of the body info.
|
||||
class Body(dict):
|
||||
def __init__(self, body):
|
||||
self['BlockIdKind'] = body['BlockId']['Kind']
|
||||
if 'Variable' in body['BlockId']:
|
||||
self['BlockName'] = body['BlockId']['Variable']['Name'][0]
|
||||
self['LineRange'] = [ body['Location'][0]['Line'], body['Location'][1]['Line'] ]
|
||||
self['Filename'] = body['Location'][0]['CacheString']
|
||||
self['Edges'] = body.get('PEdge', [])
|
||||
self['Points'] = { i+1: body['PPoint'][i]['Location']['Line'] for i in range(len(body['PPoint'])) }
|
||||
self['Index'] = body['Index']
|
||||
self['Variables'] = { x['Variable']['Name'][0]: x['Type'] for x in body['DefineVariable'] }
|
||||
|
||||
# Indexes
|
||||
self['Line2Points'] = {}
|
||||
for point, line in self['Points'].items():
|
||||
self['Line2Points'].setdefault(line, []).append(point)
|
||||
self['SrcPoint2Edges'] = {}
|
||||
for edge in self['Edges']:
|
||||
(src, dst) = edge['Index']
|
||||
self['SrcPoint2Edges'].setdefault(src, []).append(edge)
|
||||
self['Line2Edges'] = {}
|
||||
for (src, edges) in self['SrcPoint2Edges'].items():
|
||||
line = self['Points'][src]
|
||||
self['Line2Edges'].setdefault(line, []).extend(edges)
|
||||
|
||||
def edges_from_line(self, line):
|
||||
return self['Line2Edges'][line]
|
||||
|
||||
def edge_from_line(self, line):
|
||||
edges = self.edges_from_line(line)
|
||||
assert(len(edges) == 1)
|
||||
return edges[0]
|
||||
|
||||
def edges_from_point(self, point):
|
||||
return self['SrcPoint2Edges'][point]
|
||||
|
||||
def edge_from_point(self, point):
|
||||
edges = self.edges_from_point(point)
|
||||
assert(len(edges) == 1)
|
||||
return edges[0]
|
||||
|
||||
def assignment_point(self, varname):
|
||||
for edge in self['Edges']:
|
||||
if edge['Kind'] != 'Assign':
|
||||
continue
|
||||
dst = edge['Exp'][0]
|
||||
if dst['Kind'] != 'Var':
|
||||
continue
|
||||
if dst['Variable']['Name'][0] == varname:
|
||||
return edge['Index'][0]
|
||||
raise Exception("assignment to variable %s not found" % varname)
|
||||
|
||||
def assignment_line(self, varname):
|
||||
return self['Points'][self.assignment_point(varname)]
|
||||
|
||||
tests = ['test']
|
||||
tests = ['sixgill-tree', 'suppression', 'hazards']
|
||||
for name in tests:
|
||||
indir = os.path.join(testdir, name)
|
||||
outdir = os.path.join(testdir, "test-output", name)
|
||||
if not os.path.exists(outdir):
|
||||
outdir = os.path.join(testdir, 'out', name)
|
||||
try:
|
||||
os.mkdir(outdir)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def compile(source):
|
||||
cmd = "{CXX} -c {source} -fplugin={sixgill}".format(source=os.path.join(indir, source),
|
||||
CXX=cfg['CXX'], sixgill=cfg['SIXGILL_PLUGIN'])
|
||||
print("Running %s" % cmd)
|
||||
subprocess.check_call(["sh", "-c", cmd])
|
||||
|
||||
def load_db_entry(dbname, pattern):
|
||||
if not isinstance(pattern, basestring):
|
||||
output = subprocess.check_output([binpath("xdbkeys"), dbname + ".xdb"])
|
||||
entries = output.splitlines()
|
||||
matches = [f for f in entries if re.search(pattern, f)]
|
||||
if len(matches) == 0:
|
||||
raise Exception("entry not found")
|
||||
if len(matches) > 1:
|
||||
raise Exception("multiple entries found")
|
||||
pattern = matches[0]
|
||||
|
||||
output = subprocess.check_output([binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
|
||||
return json.loads(output)
|
||||
|
||||
def computeGCTypes():
|
||||
file("defaults.py", "w").write('''\
|
||||
analysis_scriptdir = '{testdir}'
|
||||
sixgill_bin = '{bindir}'
|
||||
'''.format(testdir=testdir, bindir=cfg['SIXGILL_BIN']))
|
||||
cmd = [
|
||||
os.path.join(testdir, "analyze.py"),
|
||||
"gcTypes", "--upto", "gcTypes",
|
||||
"--source=%s" % indir,
|
||||
"--objdir=%s" % outdir,
|
||||
"--js=%s" % cfg['JS_BIN'],
|
||||
]
|
||||
print("Running " + " ".join(cmd))
|
||||
output = subprocess.check_call(cmd)
|
||||
|
||||
def loadGCTypes():
|
||||
gctypes = {'GCThings': [], 'GCPointers': []}
|
||||
for line in file(os.path.join(outdir, "gcTypes.txt")):
|
||||
m = re.match(r'^(GC\w+): (.*)', line)
|
||||
if m:
|
||||
gctypes[m.group(1) + 's'].append(m.group(2))
|
||||
return gctypes
|
||||
|
||||
def process_body(body):
|
||||
return Body(body)
|
||||
|
||||
def process_bodies(bodies):
|
||||
return [ process_body(b) for b in bodies ]
|
||||
|
||||
def equal(got, expected):
|
||||
if got != expected:
|
||||
print("Got '%s', expected '%s'" % (got, expected))
|
||||
test = Test(indir, outdir, cfg)
|
||||
|
||||
os.chdir(outdir)
|
||||
subprocess.call(["sh", "-c", "rm *.xdb"])
|
||||
execfile(os.path.join(indir, "test.py"))
|
||||
execfile(os.path.join(indir, "test.py"), {'test': test, 'equal': equal})
|
||||
print("TEST-PASSED: %s" % name)
|
||||
|
89
js/src/devtools/rootAnalysis/t/hazards/source.cpp
Normal file
89
js/src/devtools/rootAnalysis/t/hazards/source.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#define ANNOTATE(property) __attribute__((tag(property)))
|
||||
|
||||
struct Cell { int f; } ANNOTATE("GC Thing");
|
||||
|
||||
class AutoSuppressGC_Base {
|
||||
public:
|
||||
AutoSuppressGC_Base() {}
|
||||
~AutoSuppressGC_Base() {}
|
||||
} ANNOTATE("Suppress GC");
|
||||
|
||||
class AutoSuppressGC_Child : public AutoSuppressGC_Base {
|
||||
public:
|
||||
AutoSuppressGC_Child() : AutoSuppressGC_Base() {}
|
||||
};
|
||||
|
||||
class AutoSuppressGC {
|
||||
AutoSuppressGC_Child helpImBeingSuppressed;
|
||||
|
||||
public:
|
||||
AutoSuppressGC() {}
|
||||
};
|
||||
|
||||
extern void GC() ANNOTATE("GC Call");
|
||||
extern void invisible();
|
||||
|
||||
void GC()
|
||||
{
|
||||
// If the implementation is too trivial, the function body won't be emitted at all.
|
||||
asm("");
|
||||
invisible();
|
||||
}
|
||||
|
||||
extern void foo(Cell*);
|
||||
|
||||
void suppressedFunction() {
|
||||
GC(); // Calls GC, but is always called within AutoSuppressGC
|
||||
}
|
||||
|
||||
void halfSuppressedFunction() {
|
||||
GC(); // Calls GC, but is sometimes called within AutoSuppressGC
|
||||
}
|
||||
|
||||
void unsuppressedFunction() {
|
||||
GC(); // Calls GC, never within AutoSuppressGC
|
||||
}
|
||||
|
||||
volatile static int x = 3;
|
||||
volatile static int* xp = &x;
|
||||
struct GCInDestructor {
|
||||
~GCInDestructor() {
|
||||
invisible();
|
||||
asm("");
|
||||
*xp = 4;
|
||||
GC();
|
||||
}
|
||||
};
|
||||
|
||||
Cell*
|
||||
f()
|
||||
{
|
||||
GCInDestructor kaboom;
|
||||
|
||||
Cell cell;
|
||||
Cell* cell1 = &cell;
|
||||
Cell* cell2 = &cell;
|
||||
Cell* cell3 = &cell;
|
||||
Cell* cell4 = &cell;
|
||||
{
|
||||
AutoSuppressGC nogc;
|
||||
suppressedFunction();
|
||||
halfSuppressedFunction();
|
||||
}
|
||||
foo(cell1);
|
||||
halfSuppressedFunction();
|
||||
foo(cell2);
|
||||
unsuppressedFunction();
|
||||
{
|
||||
// Old bug: it would look from the first AutoSuppressGC constructor it
|
||||
// found to the last destructor. This statement *should* have no effect.
|
||||
AutoSuppressGC nogc;
|
||||
}
|
||||
foo(cell3);
|
||||
Cell* cell5 = &cell;
|
||||
foo(cell5);
|
||||
|
||||
// Hazard in return value due to ~GCInDestructor
|
||||
Cell* cell6 = &cell;
|
||||
return cell6;
|
||||
}
|
36
js/src/devtools/rootAnalysis/t/hazards/test.py
Normal file
36
js/src/devtools/rootAnalysis/t/hazards/test.py
Normal file
@ -0,0 +1,36 @@
|
||||
test.compile("source.cpp")
|
||||
test.run_analysis_script('gcTypes')
|
||||
|
||||
# gcFunctions should be the inverse, but we get to rely on unmangled names here.
|
||||
gcFunctions = test.load_gcFunctions()
|
||||
print(gcFunctions)
|
||||
assert('void GC()' in gcFunctions)
|
||||
assert('void suppressedFunction()' not in gcFunctions)
|
||||
assert('void halfSuppressedFunction()' in gcFunctions)
|
||||
assert('void unsuppressedFunction()' in gcFunctions)
|
||||
assert('Cell* f()' in gcFunctions)
|
||||
|
||||
hazards = test.load_hazards()
|
||||
hazmap = {haz.variable: haz for haz in hazards}
|
||||
assert('cell1' not in hazmap)
|
||||
assert('cell2' in hazmap)
|
||||
assert('cell3' in hazmap)
|
||||
assert('cell4' not in hazmap)
|
||||
assert('cell5' not in hazmap)
|
||||
assert('cell6' not in hazmap)
|
||||
assert('<returnvalue>' in hazmap)
|
||||
|
||||
# All hazards should be in f()
|
||||
assert(hazmap['cell2'].function == 'Cell* f()')
|
||||
assert(len(set(haz.function for haz in hazards)) == 1)
|
||||
|
||||
# Check that the correct GC call is reported for each hazard. (cell3 has a
|
||||
# hazard from two different GC calls; it doesn't really matter which is
|
||||
# reported.)
|
||||
assert(hazmap['cell2'].GCFunction == 'void halfSuppressedFunction()')
|
||||
assert(hazmap['cell3'].GCFunction in ('void halfSuppressedFunction()', 'void unsuppressedFunction()'))
|
||||
assert(hazmap['<returnvalue>'].GCFunction == 'void GCInDestructor::~GCInDestructor()')
|
||||
|
||||
# Type names are handy to have in the report.
|
||||
assert(hazmap['cell2'].type == 'Cell*')
|
||||
assert(hazmap['<returnvalue>'].type == 'Cell*')
|
70
js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp
Normal file
70
js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#define ANNOTATE(property) __attribute__((tag(property)))
|
||||
|
||||
namespace js {
|
||||
namespace gc {
|
||||
struct Cell { int f; } ANNOTATE("GC Thing");
|
||||
}
|
||||
}
|
||||
|
||||
struct Bogon {
|
||||
};
|
||||
|
||||
struct JustACell : public js::gc::Cell {
|
||||
bool iHaveNoDataMembers() { return true; }
|
||||
};
|
||||
|
||||
struct JSObject : public js::gc::Cell, public Bogon {
|
||||
int g;
|
||||
};
|
||||
|
||||
struct SpecialObject : public JSObject {
|
||||
int z;
|
||||
};
|
||||
|
||||
struct ErrorResult {
|
||||
bool hasObj;
|
||||
JSObject *obj;
|
||||
void trace() {}
|
||||
} ANNOTATE("Suppressed GC Pointer");
|
||||
|
||||
struct OkContainer {
|
||||
ErrorResult res;
|
||||
bool happy;
|
||||
};
|
||||
|
||||
struct UnrootedPointer {
|
||||
JSObject *obj;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Rooted {
|
||||
T data;
|
||||
} ANNOTATE("Rooted Pointer");
|
||||
|
||||
extern void js_GC() ANNOTATE("GC Call") ANNOTATE("Slow");
|
||||
|
||||
void js_GC() {}
|
||||
|
||||
void root_arg(JSObject *obj, JSObject *random)
|
||||
{
|
||||
// Use all these types so they get included in the output.
|
||||
SpecialObject so;
|
||||
UnrootedPointer up;
|
||||
Bogon b;
|
||||
OkContainer okc;
|
||||
Rooted<JSObject*> ro;
|
||||
Rooted<SpecialObject*> rso;
|
||||
|
||||
obj = random;
|
||||
|
||||
JSObject *other1 = obj;
|
||||
js_GC();
|
||||
|
||||
float MARKER1 = 0;
|
||||
JSObject *other2 = obj;
|
||||
other1->f = 1;
|
||||
other2->f = -1;
|
||||
|
||||
unsigned int u1 = 1;
|
||||
unsigned int u2 = -1;
|
||||
}
|
60
js/src/devtools/rootAnalysis/t/sixgill-tree/test.py
Normal file
60
js/src/devtools/rootAnalysis/t/sixgill-tree/test.py
Normal file
@ -0,0 +1,60 @@
|
||||
import re
|
||||
|
||||
test.compile("source.cpp")
|
||||
test.computeGCTypes()
|
||||
body = test.process_body(test.load_db_entry("src_body", re.compile(r'root_arg'))[0])
|
||||
|
||||
# Rendering positive and negative integers
|
||||
marker1 = body.assignment_line('MARKER1')
|
||||
equal(body.edge_from_line(marker1 + 2)['Exp'][1]['String'], '1')
|
||||
equal(body.edge_from_line(marker1 + 3)['Exp'][1]['String'], '-1')
|
||||
|
||||
equal(body.edge_from_point(body.assignment_point('u1'))['Exp'][1]['String'], '1')
|
||||
equal(body.edge_from_point(body.assignment_point('u2'))['Exp'][1]['String'], '4294967295')
|
||||
|
||||
assert('obj' in body['Variables'])
|
||||
assert('random' in body['Variables'])
|
||||
assert('other1' in body['Variables'])
|
||||
assert('other2' in body['Variables'])
|
||||
|
||||
# Test function annotations
|
||||
js_GC = test.process_body(test.load_db_entry("src_body", re.compile(r'js_GC'))[0])
|
||||
annotations = js_GC['Variables']['void js_GC()']['Annotation']
|
||||
assert(annotations)
|
||||
found_call_tag = False
|
||||
for annotation in annotations:
|
||||
(annType, value) = annotation['Name']
|
||||
if annType == 'Tag' and value == 'GC Call':
|
||||
found_call_tag = True
|
||||
assert(found_call_tag)
|
||||
|
||||
# Test type annotations
|
||||
|
||||
# js::gc::Cell first
|
||||
cell = test.load_db_entry("src_comp", 'js::gc::Cell')[0]
|
||||
assert(cell['Kind'] == 'Struct')
|
||||
annotations = cell['Annotation']
|
||||
assert(len(annotations) == 1)
|
||||
(tag, value) = annotations[0]['Name']
|
||||
assert(tag == 'Tag')
|
||||
assert(value == 'GC Thing')
|
||||
|
||||
# Check JSObject inheritance.
|
||||
JSObject = test.load_db_entry("src_comp", 'JSObject')[0]
|
||||
bases = [ b['Base'] for b in JSObject['CSUBaseClass'] ]
|
||||
assert('js::gc::Cell' in bases)
|
||||
assert('Bogon' in bases)
|
||||
assert(len(bases) == 2)
|
||||
|
||||
# Check type analysis
|
||||
gctypes = test.load_gcTypes()
|
||||
assert('js::gc::Cell' in gctypes['GCThings'])
|
||||
assert('JustACell' in gctypes['GCThings'])
|
||||
assert('JSObject' in gctypes['GCThings'])
|
||||
assert('SpecialObject' in gctypes['GCThings'])
|
||||
assert('UnrootedPointer' in gctypes['GCPointers'])
|
||||
assert('Bogon' not in gctypes['GCThings'])
|
||||
assert('Bogon' not in gctypes['GCPointers'])
|
||||
assert('ErrorResult' not in gctypes['GCPointers'])
|
||||
assert('OkContainer' not in gctypes['GCPointers'])
|
||||
assert('class Rooted<JSObject*>' not in gctypes['GCPointers'])
|
63
js/src/devtools/rootAnalysis/t/sixgill.py
Normal file
63
js/src/devtools/rootAnalysis/t/sixgill.py
Normal file
@ -0,0 +1,63 @@
|
||||
#!/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/.
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
# Simplified version of the body info.
|
||||
class Body(dict):
|
||||
def __init__(self, body):
|
||||
self['BlockIdKind'] = body['BlockId']['Kind']
|
||||
if 'Variable' in body['BlockId']:
|
||||
self['BlockName'] = body['BlockId']['Variable']['Name'][0].split("$")[-1]
|
||||
loc = body['Location']
|
||||
self['LineRange'] = (loc[0]['Line'], loc[1]['Line'])
|
||||
self['Filename'] = loc[0]['CacheString']
|
||||
self['Edges'] = body.get('PEdge', [])
|
||||
self['Points'] = { i: p['Location']['Line'] for i, p in enumerate(body['PPoint'], 1) }
|
||||
self['Index'] = body['Index']
|
||||
self['Variables'] = { x['Variable']['Name'][0].split("$")[-1]: x['Type'] for x in body['DefineVariable'] }
|
||||
|
||||
# Indexes
|
||||
self['Line2Points'] = defaultdict(list)
|
||||
for point, line in self['Points'].items():
|
||||
self['Line2Points'][line].append(point)
|
||||
self['SrcPoint2Edges'] = defaultdict(list)
|
||||
for edge in self['Edges']:
|
||||
src, dst = edge['Index']
|
||||
self['SrcPoint2Edges'][src].append(edge)
|
||||
self['Line2Edges'] = defaultdict(list)
|
||||
for (src, edges) in self['SrcPoint2Edges'].items():
|
||||
line = self['Points'][src]
|
||||
self['Line2Edges'][line].extend(edges)
|
||||
|
||||
def edges_from_line(self, line):
|
||||
return self['Line2Edges'][line]
|
||||
|
||||
def edge_from_line(self, line):
|
||||
edges = self.edges_from_line(line)
|
||||
assert(len(edges) == 1)
|
||||
return edges[0]
|
||||
|
||||
def edges_from_point(self, point):
|
||||
return self['SrcPoint2Edges'][point]
|
||||
|
||||
def edge_from_point(self, point):
|
||||
edges = self.edges_from_point(point)
|
||||
assert(len(edges) == 1)
|
||||
return edges[0]
|
||||
|
||||
def assignment_point(self, varname):
|
||||
for edge in self['Edges']:
|
||||
if edge['Kind'] != 'Assign':
|
||||
continue
|
||||
dst = edge['Exp'][0]
|
||||
if dst['Kind'] != 'Var':
|
||||
continue
|
||||
if dst['Variable']['Name'][0] == varname:
|
||||
return edge['Index'][0]
|
||||
raise Exception("assignment to variable %s not found" % varname)
|
||||
|
||||
def assignment_line(self, varname):
|
||||
return self['Points'][self.assignment_point(varname)]
|
64
js/src/devtools/rootAnalysis/t/suppression/source.cpp
Normal file
64
js/src/devtools/rootAnalysis/t/suppression/source.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#define ANNOTATE(property) __attribute__((tag(property)))
|
||||
|
||||
struct Cell { int f; } ANNOTATE("GC Thing");
|
||||
|
||||
class AutoSuppressGC_Base {
|
||||
public:
|
||||
AutoSuppressGC_Base() {}
|
||||
~AutoSuppressGC_Base() {}
|
||||
} ANNOTATE("Suppress GC");
|
||||
|
||||
class AutoSuppressGC_Child : public AutoSuppressGC_Base {
|
||||
public:
|
||||
AutoSuppressGC_Child() : AutoSuppressGC_Base() {}
|
||||
};
|
||||
|
||||
class AutoSuppressGC {
|
||||
AutoSuppressGC_Child helpImBeingSuppressed;
|
||||
|
||||
public:
|
||||
AutoSuppressGC() {}
|
||||
};
|
||||
|
||||
extern void GC() ANNOTATE("GC Call");
|
||||
|
||||
void GC()
|
||||
{
|
||||
// If the implementation is too trivial, the function body won't be emitted at all.
|
||||
asm("");
|
||||
}
|
||||
|
||||
extern void foo(Cell*);
|
||||
|
||||
void suppressedFunction() {
|
||||
GC(); // Calls GC, but is always called within AutoSuppressGC
|
||||
}
|
||||
|
||||
void halfSuppressedFunction() {
|
||||
GC(); // Calls GC, but is sometimes called within AutoSuppressGC
|
||||
}
|
||||
|
||||
void unsuppressedFunction() {
|
||||
GC(); // Calls GC, never within AutoSuppressGC
|
||||
}
|
||||
|
||||
void f() {
|
||||
Cell* cell1 = nullptr;
|
||||
Cell* cell2 = nullptr;
|
||||
Cell* cell3 = nullptr;
|
||||
{
|
||||
AutoSuppressGC nogc;
|
||||
suppressedFunction();
|
||||
halfSuppressedFunction();
|
||||
}
|
||||
foo(cell1);
|
||||
halfSuppressedFunction();
|
||||
foo(cell2);
|
||||
unsuppressedFunction();
|
||||
{
|
||||
// Old bug: it would look from the first AutoSuppressGC constructor it
|
||||
// found to the last destructor. This statement *should* have no effect.
|
||||
AutoSuppressGC nogc;
|
||||
}
|
||||
foo(cell3);
|
||||
}
|
23
js/src/devtools/rootAnalysis/t/suppression/test.py
Normal file
23
js/src/devtools/rootAnalysis/t/suppression/test.py
Normal file
@ -0,0 +1,23 @@
|
||||
test.compile("source.cpp")
|
||||
test.run_analysis_script('gcTypes', upto='gcFunctions')
|
||||
|
||||
# The suppressions file uses only mangled names since it's for internal use,
|
||||
# though I may change that soon given (1) the unfortunate non-uniqueness of
|
||||
# mangled constructor names, and (2) the usefulness of this file for
|
||||
# mrgiggles's reporting.
|
||||
suppressed = test.load_suppressed_functions()
|
||||
|
||||
# Only one of these is fully suppressed (ie, *always* called within the scope
|
||||
# of an AutoSuppressGC).
|
||||
assert(len(filter(lambda f: 'suppressedFunction' in f, suppressed)) == 1)
|
||||
assert(len(filter(lambda f: 'halfSuppressedFunction' in f, suppressed)) == 0)
|
||||
assert(len(filter(lambda f: 'unsuppressedFunction' in f, suppressed)) == 0)
|
||||
|
||||
# gcFunctions should be the inverse, but we get to rely on unmangled names here.
|
||||
gcFunctions = test.load_gcFunctions()
|
||||
print(gcFunctions)
|
||||
assert('void GC()' in gcFunctions)
|
||||
assert('void suppressedFunction()' not in gcFunctions)
|
||||
assert('void halfSuppressedFunction()' in gcFunctions)
|
||||
assert('void unsuppressedFunction()' in gcFunctions)
|
||||
assert('void f()' in gcFunctions)
|
119
js/src/devtools/rootAnalysis/t/testlib.py
Normal file
119
js/src/devtools/rootAnalysis/t/testlib.py
Normal file
@ -0,0 +1,119 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from sixgill import Body
|
||||
from collections import defaultdict, namedtuple
|
||||
|
||||
scriptdir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
HazardSummary = namedtuple('HazardSummary', ['function', 'variable', 'type', 'GCFunction', 'location'])
|
||||
|
||||
|
||||
def equal(got, expected):
|
||||
if got != expected:
|
||||
print("Got '%s', expected '%s'" % (got, expected))
|
||||
|
||||
def extract_unmangled(func):
|
||||
return func.split('$')[-1]
|
||||
|
||||
|
||||
class Test(object):
|
||||
def __init__(self, indir, outdir, cfg):
|
||||
self.indir = indir
|
||||
self.outdir = outdir
|
||||
self.cfg = cfg
|
||||
|
||||
def infile(self, path):
|
||||
return os.path.join(self.indir, path)
|
||||
|
||||
def binpath(self, prog):
|
||||
return os.path.join(self.cfg.sixgill_bin, prog)
|
||||
|
||||
def compile(self, source):
|
||||
cmd = "{CXX} -c {source} -O3 -std=c++11 -fplugin={sixgill} -fplugin-arg-xgill-mangle=1".format(
|
||||
source=self.infile(source),
|
||||
CXX=self.cfg.cxx, sixgill=self.cfg.sixgill_plugin)
|
||||
if self.cfg.verbose:
|
||||
print("Running %s" % cmd)
|
||||
subprocess.check_call(["sh", "-c", cmd])
|
||||
|
||||
def load_db_entry(self, dbname, pattern):
|
||||
'''Look up an entry from an XDB database file, 'pattern' may be an exact
|
||||
matching string, or an re pattern object matching a single entry.'''
|
||||
|
||||
if not isinstance(pattern, basestring):
|
||||
output = subprocess.check_output([self.binpath("xdbkeys"), dbname + ".xdb"])
|
||||
matches = filter(lambda _: re.search(pattern, _), output.splitlines())
|
||||
if len(matches) == 0:
|
||||
raise Exception("entry not found")
|
||||
if len(matches) > 1:
|
||||
raise Exception("multiple entries found")
|
||||
pattern = matches[0]
|
||||
|
||||
output = subprocess.check_output([self.binpath("xdbfind"), "-json", dbname + ".xdb", pattern])
|
||||
return json.loads(output)
|
||||
|
||||
def run_analysis_script(self, phase, upto=None):
|
||||
file("defaults.py", "w").write('''\
|
||||
analysis_scriptdir = '{scriptdir}'
|
||||
sixgill_bin = '{bindir}'
|
||||
'''.format(scriptdir=scriptdir, bindir=self.cfg.sixgill_bin))
|
||||
cmd = [os.path.join(scriptdir, "analyze.py"), phase]
|
||||
if upto:
|
||||
cmd += ["--upto", upto]
|
||||
cmd.append("--source=%s" % self.indir)
|
||||
cmd.append("--objdir=%s" % self.outdir)
|
||||
cmd.append("--js=%s" % self.cfg.js)
|
||||
if self.cfg.verbose:
|
||||
cmd.append("--verbose")
|
||||
print("Running " + " ".join(cmd))
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
def computeGCTypes(self):
|
||||
self.run_analysis_script("gcTypes", upto="gcTypes")
|
||||
|
||||
def computeHazards(self):
|
||||
self.run_analysis_script("callgraph")
|
||||
|
||||
def load_text_file(self, filename, extract=lambda l: l):
|
||||
fullpath = os.path.join(self.outdir, filename)
|
||||
values = (extract(line.strip()) for line in file(fullpath))
|
||||
return filter(lambda _: _ is not None, values)
|
||||
|
||||
def load_suppressed_functions(self):
|
||||
return set(self.load_text_file("suppressedFunctions.lst"))
|
||||
|
||||
def load_gcTypes(self):
|
||||
def grab_type(line):
|
||||
m = re.match(r'^(GC\w+): (.*)', line)
|
||||
if m:
|
||||
return (m.group(1) + 's', m.group(2))
|
||||
return None
|
||||
|
||||
gctypes = defaultdict(list)
|
||||
for collection, typename in self.load_text_file('gcTypes.txt', extract=grab_type):
|
||||
gctypes[collection].append(typename)
|
||||
return gctypes
|
||||
|
||||
def load_gcFunctions(self):
|
||||
return self.load_text_file('gcFunctions.lst', extract=extract_unmangled)
|
||||
|
||||
def load_hazards(self):
|
||||
def grab_hazard(line):
|
||||
m = re.match(r"Function '(.*?)' has unrooted '(.*?)' of type '(.*?)' live across GC call '(.*?)' at (.*)", line)
|
||||
if m:
|
||||
info = list(m.groups())
|
||||
info[0] = info[0].split("$")[-1]
|
||||
info[3] = info[3].split("$")[-1]
|
||||
return HazardSummary(*info)
|
||||
return None
|
||||
|
||||
return self.load_text_file('rootingHazards.txt', extract=grab_hazard)
|
||||
|
||||
def process_body(self, body):
|
||||
return Body(body)
|
||||
|
||||
def process_bodies(self, bodies):
|
||||
return [self.process_body(b) for b in bodies]
|
Loading…
Reference in New Issue
Block a user