diff options
Diffstat (limited to 'js/src/devtools/rootAnalysis/t')
-rw-r--r-- | js/src/devtools/rootAnalysis/t/exceptions/source.cpp | 42 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/exceptions/test.py | 19 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/hazards/source.cpp | 186 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/hazards/test.py | 47 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp | 70 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/sixgill-tree/test.py | 60 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/sixgill.py | 63 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/suppression/source.cpp | 64 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/suppression/test.py | 23 | ||||
-rw-r--r-- | js/src/devtools/rootAnalysis/t/testlib.py | 120 |
10 files changed, 694 insertions, 0 deletions
diff --git a/js/src/devtools/rootAnalysis/t/exceptions/source.cpp b/js/src/devtools/rootAnalysis/t/exceptions/source.cpp new file mode 100644 index 000000000..14169740e --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/exceptions/source.cpp @@ -0,0 +1,42 @@ +#define ANNOTATE(property) __attribute__((tag(property))) + +struct Cell { int f; } ANNOTATE("GC Thing"); + +extern void GC() ANNOTATE("GC Call"); + +void GC() +{ + // If the implementation is too trivial, the function body won't be emitted at all. + asm(""); +} + +class RAII_GC { + public: + RAII_GC() {} + ~RAII_GC() { GC(); } +}; + +// ~AutoSomething calls GC because of the RAII_GC field. The constructor, +// though, should *not* GC -- unless it throws an exception. Which is not +// possible when compiled with -fno-exceptions. +class AutoSomething { + RAII_GC gc; + public: + AutoSomething() : gc() { + asm(""); // Ooh, scary, this might throw an exception + } + ~AutoSomething() { + asm(""); + } +}; + +extern void usevar(Cell* cell); + +void f() { + Cell* thing = nullptr; // Live range starts here + + { + AutoSomething smth; // Constructor can GC only if exceptions are enabled + usevar(thing); // Live range ends here + } // In particular, 'thing' is dead at the destructor, so no hazard +} diff --git a/js/src/devtools/rootAnalysis/t/exceptions/test.py b/js/src/devtools/rootAnalysis/t/exceptions/test.py new file mode 100644 index 000000000..f6d7f5e35 --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/exceptions/test.py @@ -0,0 +1,19 @@ +test.compile("source.cpp", '-fno-exceptions') +test.run_analysis_script('gcTypes') + +hazards = test.load_hazards() +assert(len(hazards) == 0) + +# If we compile with exceptions, then there *should* be a hazard because +# AutoSomething::AutoSomething might throw an exception, which would cause the +# partially-constructed value to be torn down, which will call ~RAII_GC. + +test.compile("source.cpp", '-fexceptions') +test.run_analysis_script('gcTypes') + +hazards = test.load_hazards() +assert(len(hazards) == 1) +hazard = hazards[0] +assert(hazard.function == 'void f()') +assert(hazard.variable == 'thing') +assert("AutoSomething::AutoSomething" in hazard.GCFunction) diff --git a/js/src/devtools/rootAnalysis/t/hazards/source.cpp b/js/src/devtools/rootAnalysis/t/hazards/source.cpp new file mode 100644 index 000000000..7f84a99db --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/hazards/source.cpp @@ -0,0 +1,186 @@ +#define ANNOTATE(property) __attribute__((tag(property))) + +struct Cell { int f; } ANNOTATE("GC Thing"); + +struct RootedCell { RootedCell(Cell*) {} } ANNOTATE("Rooted Pointer"); + +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 usecell(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(); + } + usecell(cell1); + halfSuppressedFunction(); + usecell(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; + } + usecell(cell3); + Cell* cell5 = &cell; + usecell(cell5); + + // Hazard in return value due to ~GCInDestructor + Cell* cell6 = &cell; + return cell6; +} + +Cell* copy_and_gc(Cell* src) +{ + GC(); + return reinterpret_cast<Cell*>(88); +} + +void use(Cell* cell) +{ + static int x = 0; + if (cell) + x++; +} + +struct CellContainer { + Cell* cell; + CellContainer() { + asm(""); + } +}; + +void loopy() +{ + Cell cell; + + // No hazard: haz1 is not live during call to copy_and_gc. + Cell* haz1; + for (int i = 0; i < 10; i++) { + haz1 = copy_and_gc(haz1); + } + + // No hazard: haz2 is live up to just before the GC, and starting at the + // next statement after it, but not across the GC. + Cell* haz2 = &cell; + for (int j = 0; j < 10; j++) { + use(haz2); + GC(); + haz2 = &cell; + } + + // Hazard: haz3 is live from the final statement in one iteration, across + // the GC in the next, to the use in the 2nd statement. + Cell* haz3; + for (int k = 0; k < 10; k++) { + GC(); + use(haz3); + haz3 = &cell; + } + + // Hazard: haz4 is live across a GC hidden in a loop. + Cell* haz4 = &cell; + for (int i2 = 0; i2 < 10; i2++) { + GC(); + } + use(haz4); + + // Hazard: haz5 is live from within a loop across a GC. + Cell* haz5; + for (int i3 = 0; i3 < 10; i3++) { + haz5 = &cell; + } + GC(); + use(haz5); + + // No hazard: similar to the haz3 case, but verifying that we do not get + // into an infinite loop. + Cell* haz6; + for (int i4 = 0; i4 < 10; i4++) { + GC(); + haz6 = &cell; + } + + // No hazard: haz7 is constructed within the body, so it can't make a + // hazard across iterations. Note that this requires CellContainer to have + // a constructor, because otherwise the analysis doesn't see where + // variables are declared. (With the constructor, it knows that + // construction of haz7 obliterates any previous value it might have had. + // Not that that's possible given its scope, but the analysis doesn't get + // that information.) + for (int i5 = 0; i5 < 10; i5++) { + GC(); + CellContainer haz7; + use(haz7.cell); + haz7.cell = &cell; + } + + // Hazard: make sure we *can* see hazards across iterations involving + // CellContainer; + CellContainer haz8; + for (int i6 = 0; i6 < 10; i6++) { + GC(); + use(haz8.cell); + haz8.cell = &cell; + } +} diff --git a/js/src/devtools/rootAnalysis/t/hazards/test.py b/js/src/devtools/rootAnalysis/t/hazards/test.py new file mode 100644 index 000000000..3eb08aa09 --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/hazards/test.py @@ -0,0 +1,47 @@ +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() and loopy() +assert(hazmap['cell2'].function == 'Cell* f()') +print(len(set(haz.function for haz in hazards))) +assert(len(set(haz.function for haz in hazards)) == 2) + +# 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*') + +# loopy hazards. See comments in source. +assert('haz1' not in hazmap); +assert('haz2' not in hazmap); +assert('haz3' in hazmap); +assert('haz4' in hazmap); +assert('haz5' in hazmap); +assert('haz6' not in hazmap); +assert('haz7' not in hazmap); +assert('haz8' in hazmap); diff --git a/js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp b/js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp new file mode 100644 index 000000000..2de9ef4bb --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/sixgill-tree/source.cpp @@ -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; +} diff --git a/js/src/devtools/rootAnalysis/t/sixgill-tree/test.py b/js/src/devtools/rootAnalysis/t/sixgill-tree/test.py new file mode 100644 index 000000000..c0c0263cd --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/sixgill-tree/test.py @@ -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']) diff --git a/js/src/devtools/rootAnalysis/t/sixgill.py b/js/src/devtools/rootAnalysis/t/sixgill.py new file mode 100644 index 000000000..2bdf76a49 --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/sixgill.py @@ -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)] diff --git a/js/src/devtools/rootAnalysis/t/suppression/source.cpp b/js/src/devtools/rootAnalysis/t/suppression/source.cpp new file mode 100644 index 000000000..e7b41b4cb --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/suppression/source.cpp @@ -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); +} diff --git a/js/src/devtools/rootAnalysis/t/suppression/test.py b/js/src/devtools/rootAnalysis/t/suppression/test.py new file mode 100644 index 000000000..65974cc33 --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/suppression/test.py @@ -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) diff --git a/js/src/devtools/rootAnalysis/t/testlib.py b/js/src/devtools/rootAnalysis/t/testlib.py new file mode 100644 index 000000000..438398f1e --- /dev/null +++ b/js/src/devtools/rootAnalysis/t/testlib.py @@ -0,0 +1,120 @@ +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, options = ''): + cmd = "{CXX} -c {source} -O3 -std=c++11 -fplugin={sixgill} -fplugin-arg-xgill-mangle=1 {options}".format( + source=self.infile(source), + CXX=self.cfg.cxx, sixgill=self.cfg.sixgill_plugin, + options=options) + 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("gcTypes") + + 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] |