Bug 1246804 - Switch to using in-source annotations. Use C++ inheritance information when describing GC types. Add a test suite., r=terrence

MozReview-Commit-ID: HCcG2k8Wyb9

--HG--
extra : rebase_source : 732ef6ecc52f0e528d38b8c42e442919eba9b5ae
extra : source : 901b1c651c982ccbf42604231c723d168e1cde69
This commit is contained in:
Steve Fink 2015-09-29 13:39:33 -07:00
parent 3608515672
commit 517605b022
22 changed files with 498 additions and 126 deletions

View File

@ -24,6 +24,7 @@
#include <stdarg.h>
#include "js/GCAnnotations.h"
#include "js/Value.h"
#include "nscore.h"
#include "nsStringGlue.h"

View File

@ -9,6 +9,7 @@
#include "mozilla/Vector.h"
#include "js/GCAnnotations.h"
#include "js/HeapAPI.h"
#include "js/UniquePtr.h"
@ -581,7 +582,7 @@ class JS_PUBLIC_API(AutoCheckCannotGC) : public AutoAssertOnGC
public:
AutoCheckCannotGC() : AutoAssertOnGC() {}
explicit AutoCheckCannotGC(JSRuntime* rt) : AutoAssertOnGC(rt) {}
};
} JS_HAZ_GC_INVALIDATED;
/**
* Unsets the gray bit for anything reachable from |thing|. |kind| should not be

53
js/public/GCAnnotations.h Normal file
View File

@ -0,0 +1,53 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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/. */
#ifndef js_GCAnnotations_h
#define js_GCAnnotations_h
// Set of annotations for the rooting hazard analysis, used to categorize types
// and functions.
#ifdef XGILL_PLUGIN
// Mark a type as being a GC thing (eg js::gc::Cell has this annotation).
# define JS_HAZ_GC_THING __attribute__((tag("GC Thing")))
// Mark a type as holding a pointer to a GC thing (eg JS::Value has this
// annotation.)
# define JS_HAZ_GC_POINTER __attribute__((tag("GC Pointer")))
// Mark a type as a rooted pointer, suitable for use on the stack (eg all
// Rooted<T> instantiations should have this.)
# define JS_HAZ_ROOTED __attribute__((tag("Rooted Pointer")))
// Mark a type as something that should not be held live across a GC, but which
// is itself not a GC pointer.
# define JS_HAZ_GC_INVALIDATED __attribute__((tag("Invalidated by GC")))
// Mark a type that would otherwise be considered a GC Pointer (eg because it
// contains a JS::Value field) as a non-GC pointer. It is handled almost the
// same in the analysis as a rooted pointer, except it will not be reported as
// an unnecessary root if used across a GC call. This should rarely be used,
// but makes sense for something like ErrorResult, which only contains a GC
// pointer when it holds an exception (and it does its own rooting,
// conditionally.)
# define JS_HAZ_NON_GC_POINTER __attribute__((tag("Suppressed GC Pointer")))
// Mark a function as something that runs a garbage collection, potentially
// invalidating GC pointers.
# define JS_HAZ_GC_CALL __attribute__((tag("GC Call")))
#else
# define JS_HAZ_GC_THING
# define JS_HAZ_GC_POINTER
# define JS_HAZ_ROOTED
# define JS_HAZ_GC_INVALIDATED
# define JS_HAZ_NON_GC_POINTER
# define JS_HAZ_GC_CALL
#endif
#endif /* js_GCAnnotations_h */

View File

@ -32,7 +32,7 @@ struct jsid
size_t asBits;
bool operator==(jsid rhs) const { return asBits == rhs.asBits; }
bool operator!=(jsid rhs) const { return asBits != rhs.asBits; }
};
} JS_HAZ_GC_POINTER;
#define JSID_BITS(id) (id.asBits)
#define JSID_TYPE_STRING 0x0

View File

@ -16,6 +16,7 @@
#include "jspubtd.h"
#include "js/GCAnnotations.h"
#include "js/GCAPI.h"
#include "js/GCPolicyAPI.h"
#include "js/HeapAPI.h"
@ -707,7 +708,7 @@ class MOZ_RAII Rooted : public js::RootedBase<T>
MaybeWrapped ptr;
Rooted(const Rooted&) = delete;
};
} JS_HAZ_ROOTED;
} /* namespace JS */
@ -1053,7 +1054,7 @@ class PersistentRooted : public js::PersistentRootedBase<T>,
T>::Type;
MaybeWrapped ptr;
};
} JS_HAZ_ROOTED;
class JS_PUBLIC_API(ObjectPtr)
{

View File

@ -1395,7 +1395,7 @@ class Value
friend jsval_layout (::JSVAL_TO_IMPL)(Value);
friend Value JS_VALUE_CONSTEXPR (::IMPL_TO_JSVAL)(jsval_layout l);
friend Value JS_VALUE_CONSTEXPR (JS::UndefinedValue)();
};
} JS_HAZ_GC_POINTER;
inline bool
IsOptimizedPlaceholderMagicValue(const Value& v)

View File

@ -4,12 +4,12 @@
var functionBodies;
function findAllPoints(blockId)
function findAllPoints(bodies, blockId)
{
var points = [];
var body;
for (var xbody of functionBodies) {
for (var xbody of bodies) {
if (sameBlockId(xbody.BlockId, blockId)) {
assert(!body);
body = xbody;
@ -22,7 +22,7 @@ function findAllPoints(blockId)
for (var edge of body.PEdge) {
points.push([body, edge.Index[0]]);
if (edge.Kind == "Loop")
Array.prototype.push.apply(points, findAllPoints(edge.BlockId));
Array.prototype.push.apply(points, findAllPoints(bodies, edge.BlockId));
}
return points;
@ -55,7 +55,7 @@ function isMatchingDestructor(constructor, edge)
// treat each instance separately, such as when different regions of a function
// body were guarded by these constructors and you needed to do something
// different with each.)
function allRAIIGuardedCallPoints(body, isConstructor)
function allRAIIGuardedCallPoints(bodies, body, isConstructor)
{
if (!("PEdge" in body))
return [];
@ -77,7 +77,7 @@ function allRAIIGuardedCallPoints(body, isConstructor)
if (edge.PEdgeCallInstance.Exp.Kind != "Var")
continue;
Array.prototype.push.apply(points, pointsInRAIIScope(body, edge));
Array.prototype.push.apply(points, pointsInRAIIScope(bodies, body, edge));
}
return points;
@ -133,7 +133,7 @@ function findMatchingConstructor(destructorEdge, body)
debugger;
}
function pointsInRAIIScope(body, constructorEdge) {
function pointsInRAIIScope(bodies, body, constructorEdge) {
var seen = {};
var worklist = [constructorEdge.Index[1]];
var points = [];
@ -150,7 +150,7 @@ function pointsInRAIIScope(body, constructorEdge) {
if (isMatchingDestructor(constructorEdge, nedge))
continue;
if (nedge.Kind == "Loop")
Array.prototype.push.apply(points, findAllPoints(nedge.BlockId));
Array.prototype.push.apply(points, findAllPoints(bodies, nedge.BlockId));
worklist.push(nedge.Index[1]);
}
}

4
js/src/devtools/rootAnalysis/analyze.py Executable file → Normal file
View File

@ -200,6 +200,10 @@ parser.add_argument('step', metavar='STEP', type=str, nargs='?',
help='run starting from this step')
parser.add_argument('--source', metavar='SOURCE', type=str, nargs='?',
help='source code to analyze')
parser.add_argument('--objdir', metavar='DIR', type=str, nargs='?',
help='object directory of compiled files')
parser.add_argument('--js', metavar='JSSHELL', type=str, nargs='?',
help='full path to ctypes-capable JS shell')
parser.add_argument('--upto', metavar='UPTO', type=str, nargs='?',
help='last step to execute')
parser.add_argument('--jobs', '-j', default=None, metavar='JOBS', type=int,

View File

@ -694,7 +694,7 @@ function process(name, json) {
for (var body of functionBodies)
body.suppressed = [];
for (var body of functionBodies) {
for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
pbody.suppressed[id] = true;
}
processBodies(functionName);

View File

@ -351,57 +351,9 @@ function isOverridableField(initialCSU, csu, field)
return true;
}
function listGCTypes() {
return [
'js::gc::Cell',
'JSObject',
'JSString',
'JSFatInlineString',
'JSExternalString',
'js::Shape',
'js::AccessorShape',
'js::BaseShape',
'JSScript',
'js::ObjectGroup',
'js::LazyScript',
'js::jit::JitCode',
'JS::Symbol',
];
}
function listGCPointers() {
return [
'JS::Value',
'jsid',
'js::TypeSet',
'js::TypeSet::ObjectKey',
'js::TypeSet::Type',
// AutoCheckCannotGC should also not be held live across a GC function.
'JS::AutoCheckCannotGC',
];
}
function listNonGCTypes() {
return [
];
}
function listNonGCPointers() {
return [
// Both of these are safe only because jsids are currently only made
// from "interned" (pinned) strings. Once that changes, both should be
// removed from the list.
// Safe only because jsids are currently only made from pinned strings.
'NPIdentifier',
'XPCNativeMember',
];
}
// Flexible mechanism for deciding an arbitrary type is a GCPointer. Its one
// use turned out to be unnecessary due to another change, but the mechanism
// seems useful for something like /Vector.*Something/.
function isGCPointer(typeName)
{
return false;
}

View File

@ -196,11 +196,49 @@ function printOnce(line)
}
}
function processBody(caller, body)
// Returns a table mapping function name to lists of [annotation-name,
// annotation-value] pairs: { function-name => [ [annotation-name, annotation-value] ] }
function getAnnotations(body)
{
var all_annotations = {};
for (var v of (body.DefineVariable || [])) {
if (v.Variable.Kind != 'Func')
continue;
var name = v.Variable.Name[0];
var annotations = all_annotations[name] = [];
for (var ann of (v.Type.Annotation || [])) {
annotations.push(ann.Name);
}
}
return all_annotations;
}
function getTags(functionName, body) {
var tags = new Set();
var annotations = getAnnotations(body);
print(functionName);
print(JSON.stringify(annotations));
if (functionName in annotations) {
print("crawling through");
for (var [ annName, annValue ] of annotations[functionName]) {
print(` got ${annName}: ${annValue}`);
if (annName == 'Tag')
tags.add(annValue);
}
}
return tags;
}
function processBody(functionName, body)
{
if (!('PEdge' in body))
return;
for (var tag of getTags(functionName, body).values())
print("T " + memo(functionName) + " " + tag);
lastline = null;
for (var edge of body.PEdge) {
if (edge.Kind != "Call")
@ -213,7 +251,7 @@ function processBody(caller, body)
}
for (var callee of getCallees(edge)) {
var prologue = (edgeSuppressed || callee.suppressed) ? "SUPPRESS_GC " : "";
prologue += memo(caller) + " ";
prologue += memo(functionName) + " ";
if (callee.kind == 'direct') {
if (!(callee.name in seen)) {
seen[callee.name] = true;
@ -283,21 +321,18 @@ if (theFunctionNameToFind) {
minStream = maxStream = index;
}
for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
var name = xdb.read_key(nameIndex);
var data = xdb.read_entry(name);
functionBodies = JSON.parse(data.readString());
function process(functionName, functionBodies)
{
for (var body of functionBodies)
body.suppressed = [];
for (var body of functionBodies) {
for (var [pbody, id] of allRAIIGuardedCallPoints(body, isSuppressConstructor))
for (var [pbody, id] of allRAIIGuardedCallPoints(functionBodies, body, isSuppressConstructor))
pbody.suppressed[id] = true;
}
seenCallees = {};
seenSuppressedCallees = {};
var functionName = name.readString();
for (var body of functionBodies)
processBody(functionName, body);
@ -364,7 +399,12 @@ for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
print("D " + memo(C3) + " " + memo(mangled));
}
}
}
for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
var name = xdb.read_key(nameIndex);
var data = xdb.read_entry(name);
process(name.readString(), JSON.parse(data.readString()));
xdb.free_string(name);
xdb.free_string(data);
}

View File

@ -5,13 +5,30 @@
loadRelativeToScript('utility.js');
loadRelativeToScript('annotations.js');
var annotatedGCPointers = [];
var annotations = {
'GCPointers': [],
'GCThings': [],
'NonGCTypes': {}, // unused
'NonGCPointers': {},
'RootedPointers': {},
};
var structureParents = {}; // Map from field => list of <parent, fieldName>
var pointerParents = {}; // Map from field => list of <parent, fieldName>
var baseClasses = {}; // Map from struct name => list of base class name strings
var gcTypes = {}; // map from parent struct => Set of GC typed children
var gcPointers = {}; // map from parent struct => Set of GC typed children
var gcFields = new Map;
var rootedPointers = {};
function processCSU(csu, body)
{
if (!("DataField" in body))
return;
for (var field of body.DataField) {
for (let { 'Base': base } of (body.CSUBaseClass || []))
addBaseClass(csu, base);
for (let field of (body.DataField || [])) {
var type = field.Field.Type;
var fieldName = field.Field.Name[0];
if (type.Kind == "Pointer") {
@ -32,20 +49,44 @@ function processCSU(csu, body)
addNestedStructure(csu, type.Name, fieldName);
}
}
if (isGCPointer(csu))
annotatedGCPointers.push(csu);
for (let { 'Name': [ annType, tag ] } of (body.Annotation || [])) {
if (annType != 'Tag')
continue;
if (tag == 'GC Pointer')
annotations.GCPointers.push(csu);
else if (tag == 'Invalidated by GC')
annotations.GCPointers.push(csu);
else if (tag == 'GC Thing')
annotations.GCThings.push(csu);
else if (tag == 'Suppressed GC Pointer')
annotations.NonGCPointers[csu] = true;
else if (tag == 'Rooted Pointer')
annotations.RootedPointers[csu] = true;
}
}
var structureParents = {}; // Map from field => list of <parent, fieldName>
var pointerParents = {}; // Map from field => list of <parent, fieldName>
// csu.field is of type inner
function addNestedStructure(csu, inner, field)
{
if (!(inner in structureParents))
structureParents[inner] = [];
if (field.match(/^field:\d+$/) && (csu in baseClasses) && (baseClasses[csu].indexOf(inner) != -1))
return;
structureParents[inner].push([ csu, field ]);
}
function addBaseClass(csu, base) {
if (!(csu in baseClasses))
baseClasses[csu] = [];
baseClasses[csu].push(base);
var k = baseClasses[csu].length;
addNestedStructure(csu, base, `<base-${k}>`);
}
function addNestedPointer(csu, inner, field)
{
if (!(inner in pointerParents))
@ -70,11 +111,12 @@ for (var csuIndex = minStream; csuIndex <= maxStream; csuIndex++) {
xdb.free_string(data);
}
var gcTypes = {}; // map from parent struct => Set of GC typed children
var gcPointers = {}; // map from parent struct => Set of GC typed children
var nonGCTypes = {}; // set of types that would ordinarily be GC types but we are suppressing
var nonGCPointers = {}; // set of types that would ordinarily be GC pointers but we are suppressing
var gcFields = new Map;
// Now that we have the whole hierarchy set up, add all the types and propagate
// info.
for (let csu of annotations.GCThings)
addGCType(csu);
for (let csu of annotations.GCPointers)
addGCPointer(csu);
function stars(n) { return n ? '*' + stars(n-1) : '' };
@ -122,13 +164,13 @@ function markGCType(typeName, child, why, typePtrLevel, fieldPtrLevel, indent)
return;
if (ptrLevel == 0) {
if (typeName in nonGCTypes)
if (typeName in annotations.NonGCTypes)
return;
if (!(typeName in gcTypes))
gcTypes[typeName] = new Set();
gcTypes[typeName].add(why);
} else if (ptrLevel == 1) {
if (typeName in nonGCPointers)
if (typeName in annotations.NonGCPointers)
return;
if (!(typeName in gcPointers))
gcPointers[typeName] = new Set();
@ -165,17 +207,8 @@ function addGCPointer(typeName)
markGCType(typeName, '<pointer-annotation>', '(annotation)', 1, 0, "");
}
for (var type of listNonGCTypes())
nonGCTypes[type] = true;
for (var type of listNonGCPointers())
nonGCPointers[type] = true;
for (var type of listGCTypes())
addGCType(type);
for (var type of listGCPointers())
addGCPointer(type);
for (var typeName of annotatedGCPointers)
addGCPointer(typeName);
//for (var type of listNonGCPointers())
// annotations.NonGCPointers[type] = true;
function explain(csu, indent, seen) {
if (!seen)
@ -186,25 +219,27 @@ function explain(csu, indent, seen) {
var fields = gcFields.get(csu);
if (fields.has('<annotation>')) {
print(indent + "which is a GCThing because I said so");
print(indent + "which is annotated as a GCThing");
return;
}
if (fields.has('<pointer-annotation>')) {
print(indent + "which is a GCPointer because I said so");
print(indent + "which is annotated as a GCPointer");
return;
}
for (var [ field, [ child, ptrdness ] ] of fields) {
var inherit = "";
if (field == "field:0")
inherit = " (probably via inheritance)";
var msg = indent + "contains field '" + field + "' ";
if (ptrdness == -1)
msg += "(with a pointer to unsafe storage) holding a ";
else if (ptrdness == 0)
msg += "of type ";
else
msg += "pointing to type ";
msg += child + inherit;
var msg = indent;
if (field[0] == '<')
msg += "inherits from ";
else {
msg += "contains field '" + field + "' ";
if (ptrdness == -1)
msg += "(with a pointer to unsafe storage) holding a ";
else if (ptrdness == 0)
msg += "of type ";
else
msg += "pointing to type ";
}
msg += child;
print(msg);
if (!seen.has(child))
explain(child, indent + " ", seen);

View File

@ -74,6 +74,8 @@ function loadCallgraph(file)
var suppressedFieldCalls = {};
var resolvedFunctions = {};
var numGCCalls = 0;
for (var line of readFileLines_gen(file)) {
line = line.replace(/\n/, "");
@ -119,6 +121,13 @@ function loadCallgraph(file)
var callee = idToMangled[match[2]];
addCallEdge(callerField, callee, false);
resolvedFunctions[callerField] = true;
} else if (match = tag == 'T' && /^T (\d+) (.*)/.exec(line)) {
var mangled = idToMangled[match[1]];
var tag = match[2];
if (tag == 'GC Call') {
addGCFunction(mangled, "GC");
numGCCalls++;
}
}
}
@ -161,13 +170,11 @@ function loadCallgraph(file)
suppressedFunctions[name] = true;
}
for (var gcName of [ 'void js::gc::GCRuntime::collect(uint8, js::SliceBudget, uint32)',
'void js::gc::GCRuntime::minorGC(uint32)',
'void js::gc::GCRuntime::minorGC(uint32)' ])
{
assert(gcName in mangledName, "GC function not found: " + gcName);
addGCFunction(mangledName[gcName], "GC");
}
// Sanity check to make sure the callgraph has some functions annotated as
// GC Calls. This is mostly a check to be sure the earlier processing
// succeeded (as opposed to, say, running on empty xdb files because you
// didn't actually compile anything interesting.)
assert(numGCCalls > 0, "No GC functions found!");
// Initialize the worklist to all known gcFunctions.
var worklist = [];

View File

@ -0,0 +1,147 @@
import sys
import os
import re
import json
import subprocess
testdir = os.path.abspath(os.path.dirname(__file__))
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"]
def binpath(prog):
return os.path.join(cfg['SIXGILL_BIN'], prog)
if not os.path.exists("test-output"):
os.mkdir("test-output")
# 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']
for name in tests:
indir = os.path.join(testdir, name)
outdir = os.path.join(testdir, "test-output", name)
if not os.path.exists(outdir):
os.mkdir(outdir)
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))
os.chdir(outdir)
subprocess.call(["sh", "-c", "rm *.xdb"])
execfile(os.path.join(indir, "test.py"))
print("TEST-PASSED: %s" % name)

View 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;
}

View File

@ -0,0 +1,58 @@
compile("source.cpp")
computeGCTypes()
body = process_body(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 = process_body(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 = 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 = 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 = loadGCTypes()
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'])

View File

@ -17,6 +17,7 @@
#include "gc/Statistics.h"
#include "gc/StoreBuffer.h"
#include "gc/Tracer.h"
#include "js/GCAnnotations.h"
namespace js {
@ -870,7 +871,7 @@ class GCRuntime
Finished
};
void minorGCImpl(JS::gcreason::Reason reason, Nursery::ObjectGroupList* pretenureGroups);
void minorGCImpl(JS::gcreason::Reason reason, Nursery::ObjectGroupList* pretenureGroups) JS_HAZ_GC_CALL;
// For ArenaLists::allocateFromArena()
friend class ArenaLists;
@ -916,7 +917,7 @@ class GCRuntime
bool checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason);
gcstats::ZoneGCStats scanZonesBeforeGC();
void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason);
void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason) JS_HAZ_GC_CALL;
bool gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::Reason reason);
void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason);

View File

@ -27,6 +27,7 @@
#include "gc/Memory.h"
#include "js/GCAPI.h"
#include "js/HeapAPI.h"
#include "js/RootingAPI.h"
#include "js/TracingAPI.h"
struct JSRuntime;
@ -247,7 +248,7 @@ struct Cell
protected:
inline uintptr_t address() const;
inline Chunk* chunk() const;
};
} JS_HAZ_GC_THING;
// A GC TenuredCell gets behaviors that are valid for things in the Tenured
// heap, such as access to the arena and mark bits.

View File

@ -33,7 +33,7 @@ with Files('jit/**'):
for gcfile in ['jsgc*', 'devtools/rootAnalysis', 'devtools/gc-ubench', 'devtools/gctrace']:
with Files(gcfile):
BUG_COMPONENT = component_gc
for header in ('GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
for header in ('GCAnnotations.h', 'GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
with Files('../public/' + header):
BUG_COMPONENT = component_gc
@ -108,6 +108,7 @@ EXPORTS.js += [
'../public/Conversions.h',
'../public/Date.h',
'../public/Debug.h',
'../public/GCAnnotations.h',
'../public/GCAPI.h',
'../public/GCHashTable.h',
'../public/GCPolicyAPI.h',

View File

@ -267,7 +267,7 @@ class TypeSet
void ensureTrackedProperty(JSContext* cx, jsid id);
ObjectGroup* maybeGroup();
};
} JS_HAZ_GC_POINTER;
// Information about a single concrete type. We pack this into one word,
// where small values are particular primitive or other singleton types and
@ -356,7 +356,7 @@ class TypeSet
bool operator == (Type o) const { return data == o.data; }
bool operator != (Type o) const { return data != o.data; }
};
} JS_HAZ_GC_POINTER;
static inline Type UndefinedType() { return Type(JSVAL_TYPE_UNDEFINED); }
static inline Type NullType() { return Type(JSVAL_TYPE_NULL); }
@ -535,7 +535,7 @@ class TypeSet
static bool IsTypeMarked(Type* v);
static bool IsTypeAllocatedDuringIncremental(Type v);
static bool IsTypeAboutToBeFinalized(Type* v);
};
} JS_HAZ_GC_POINTER;
/*
* A constraint which listens to additions to a type set and propagates those

View File

@ -1344,7 +1344,7 @@ private:
// creation of XPCNativeInterfaces which have more than 2^12 members.
// If the width of this field changes, update GetMaxIndexInInterface.
uint16_t mIndexInInterface : 12;
};
} JS_HAZ_NON_GC_POINTER; // Only stores a pinned string
/***************************************************************************/
// XPCNativeInterface represents a single idl declared interface. This is

2
testing/mozharness/scripts/spidermonkey/build.shell Executable file → Normal file
View File

@ -5,5 +5,5 @@ set -x
[ -d $ANALYZED_OBJDIR ] || mkdir $ANALYZED_OBJDIR
cd $ANALYZED_OBJDIR
$SOURCE/js/src/configure --enable-debug --enable-optimize --enable-stdcxx-compat --enable-ctypes --enable-exact-rooting --enable-gcgenerational --with-system-nspr
$SOURCE/js/src/configure --enable-debug --enable-optimize --enable-stdcxx-compat --enable-ctypes --with-system-nspr
make -j12 -s