Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit f34f26f

Browse files
committed
No bug, DONTBUILD. Updates to the static rooting analysis, including transition from Makefile to python script.
Although this patch contains some updates to Makefile.in, I am no longer using it at all. I now run analyze.py for better control, though note that it depends on loading in some configuration settings that are hardcoded to my environment. This patch also contains a number of updates to the annotations. --HG-- extra : rebase_source : ebd4deb590fb9fde4532bdf45214ffca117e1c3a
1 parent df48e96 commit f34f26f

6 files changed

Lines changed: 237 additions & 63 deletions

File tree

js/src/devtools/rootAnalysis/Makefile.in

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,36 @@ src_body.xdb src_comp.xdb: run_complete
5151

5252
callgraph.txt: src_body.xdb src_comp.xdb computeCallgraph.js
5353
@echo Started computation of $@ at $$(date)
54-
$(CALL_JS) $(ANALYSIS_SCRIPT_DIR)/computeCallgraph.js > $@
54+
$(CALL_JS) $(ANALYSIS_SCRIPT_DIR)/computeCallgraph.js > $@.tmp
55+
mv $@.tmp $@
5556
@echo Finished computation of $@ at $$(date)
5657

5758
gcFunctions.txt: callgraph.txt computeGCFunctions.js annotations.js
5859
@echo Started computation of $@ at $$(date)
59-
$(CALL_JS) $(ANALYSIS_SCRIPT_DIR)/computeGCFunctions.js ./callgraph.txt > $@
60+
$(CALL_JS) $(ANALYSIS_SCRIPT_DIR)/computeGCFunctions.js ./callgraph.txt > $@.tmp
61+
mv $@.tmp $@
6062
@echo Finished computation of $@ at $$(date)
6163

64+
gcFunctions.lst: gcFunctions.txt
65+
perl -lne 'print $$1 if /^GC Function: (.*)/' gcFunctions.txt > $@
66+
67+
suppressedFunctions.lst: gcFunctions.txt
68+
perl -lne 'print $$1 if /^Suppressed Function: (.*)/' gcFunctions.txt > $@
69+
6270
gcTypes.txt: src_comp.xdb computeGCTypes.js annotations.js
6371
@echo Started computation of $@ at $$(date)
64-
$(CALL_JS) $(ANALYSIS_SCRIPT_DIR)/computeGCTypes.js > $@
72+
$(CALL_JS) $(ANALYSIS_SCRIPT_DIR)/computeGCTypes.js > $@.tmp
73+
mv $@.tmp $@
6574
@echo Finished computation of $@ at $$(date)
6675

6776
allFunctions.txt: src_body.xdb
6877
@echo Started computation of $@ at $$(date)
69-
time $(SIXGILL)/bin/xdbkeys $^ > $@
78+
time $(SIXGILL)/bin/xdbkeys $^ > $@.tmp
79+
mv $@.tmp $@
7080
@echo Finished computation of $@ at $$(date)
7181

72-
rootingHazards.txt: gcFunctions.txt gcTypes.txt analyzeRoots.js annotations.js gen-hazards.sh
82+
rootingHazards.txt: gcFunctions.lst suppressedFunctions.lst gcTypes.txt analyzeRoots.js annotations.js gen-hazards.sh
7383
@echo Started computation of $@ at $$(date)
74-
time env JS=$(JS) ANALYZE="$(ANALYSIS_SCRIPT_DIR)/analyzeRoots.js" SIXGILL="$(SIXGILL)" "$(ANALYSIS_SCRIPT_DIR)/gen-hazards.sh" $(JOBS) > $@
84+
time env JS=$(JS) ANALYZE="$(ANALYSIS_SCRIPT_DIR)/analyzeRoots.js" SIXGILL="$(SIXGILL)" "$(ANALYSIS_SCRIPT_DIR)/gen-hazards.sh" $(JOBS) > $@.tmp
85+
mv $@.tmp $@
7586
@echo Finished computation of $@ at $$(date)
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#!/usr/bin/python
2+
3+
#
4+
# This Source Code Form is subject to the terms of the Mozilla Public
5+
# License, v. 2.0. If a copy of the MPL was not distributed with this
6+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
8+
"""
9+
Runs the static rooting analysis
10+
"""
11+
12+
from subprocess import Popen
13+
import subprocess
14+
import os
15+
import argparse
16+
import sys
17+
18+
def env(config):
19+
e = os.environ
20+
e['PATH'] = '%s:%s/bin' % (e['PATH'], config['sixgill'])
21+
e['XDB'] = '%(sixgill)s/bin/xdb.so' % config
22+
e['SOURCE_ROOT'] = e['TARGET']
23+
return e
24+
25+
def fill(command, config):
26+
try:
27+
return tuple(s % config for s in command)
28+
except:
29+
print("Substitution failed:")
30+
for fragment in command:
31+
try:
32+
fragment % config
33+
except:
34+
print(" %s" % fragment)
35+
raise Hell
36+
37+
def generate_hazards(config, outfilename):
38+
jobs = []
39+
for i in range(config['jobs']):
40+
command = fill(('%(js)s',
41+
'%(analysis_scriptdir)s/analyzeRoots.js',
42+
'%(gcFunctions_list)s',
43+
'%(suppressedFunctions_list)s',
44+
'%(gcTypes)s',
45+
str(i+1), '%(jobs)s',
46+
'tmp.%s' % (i+1,)),
47+
config)
48+
outfile = 'rootingHazards.%s' % (i+1,)
49+
output = open(outfile, 'w')
50+
print(' '.join(command) + ' > ' + outfile)
51+
jobs.append((command, Popen(command, stdout=output, env=env(config))))
52+
53+
final_status = 0
54+
while jobs:
55+
pid, status = os.wait()
56+
jobs = [ job for job in jobs if job[1].pid != pid ]
57+
final_status = final_status or status
58+
59+
if final_status:
60+
raise subprocess.CalledProcessError(final_status, 'analyzeRoots.js')
61+
62+
with open(outfilename, 'w') as output:
63+
command = ['cat'] + [ 'rootingHazards.%s' % (i+1,) for i in range(config['jobs']) ]
64+
print(' '.join(command) + ' > ' + outfilename)
65+
subprocess.call(command, stdout=output)
66+
67+
JOBS = { 'dbs':
68+
(('%(CWD)s/run_complete',
69+
'--foreground',
70+
'--build-root=%(objdir)s',
71+
'--work=dir=work',
72+
'-b', '%(sixgill)s/bin',
73+
'--buildcommand=%(buildcommand)s',
74+
'.'),
75+
None),
76+
77+
'callgraph':
78+
(('%(js)s', '%(analysis_scriptdir)s/computeCallgraph.js'),
79+
'callgraph.txt'),
80+
81+
'gcFunctions':
82+
(('%(js)s', '%(analysis_scriptdir)s/computeGCFunctions.js', '%(callgraph)s'),
83+
'gcFunctions.txt'),
84+
85+
'gcFunctions_list':
86+
(('perl', '-lne', 'print $1 if /^GC Function: (.*)/', '%(gcFunctions)s'),
87+
'gcFunctions.lst'),
88+
89+
'suppressedFunctions_list':
90+
(('perl', '-lne', 'print $1 if /^Suppressed Function: (.*)/', '%(gcFunctions)s'),
91+
'suppressedFunctions.lst'),
92+
93+
'gcTypes':
94+
(('%(js)s', '%(analysis_scriptdir)s/computeGCTypes.js',),
95+
'gcTypes.txt'),
96+
97+
'allFunctions':
98+
(('%(sixgill)s/bin/xdbkeys', 'src_body.xdb',),
99+
'allFunctions.txt'),
100+
101+
'hazards':
102+
(generate_hazards, 'rootingHazards.txt')
103+
}
104+
105+
def run_job(name, config):
106+
command, outfilename = JOBS[name]
107+
print("Running " + name + " to generate " + str(outfilename))
108+
if hasattr(command, '__call__'):
109+
command(config, outfilename)
110+
else:
111+
command = fill(command, config)
112+
print(' '.join(command))
113+
temp = '%s.tmp' % name
114+
with open(temp, 'w') as output:
115+
subprocess.check_call(command, stdout=output, env=env(config))
116+
if outfilename is not None:
117+
os.rename(temp, outfilename)
118+
119+
config = { 'CWD': os.path.dirname(__file__) }
120+
121+
defaults = [ '%s/defaults.py' % config['CWD'] ]
122+
123+
for default in defaults:
124+
try:
125+
execfile(default, config)
126+
except:
127+
pass
128+
129+
data = config.copy()
130+
131+
parser = argparse.ArgumentParser(description='Statically analyze build tree for rooting hazards.')
132+
parser.add_argument('target', metavar='TARGET', type=str, nargs='?',
133+
help='run starting from this target')
134+
parser.add_argument('--jobs', '-j', default=4, metavar='JOBS', type=int,
135+
help='number of simultaneous analyzeRoots.js jobs')
136+
parser.add_argument('--list', const=True, nargs='?', type=bool,
137+
help='display available targets')
138+
parser.add_argument('--buildcommand', '--build', '-b', type=str, nargs='?',
139+
help='command to build the tree being analyzed')
140+
parser.add_argument('--tag', '-t', type=str, nargs='?',
141+
help='name of job, also sets build command to "build.<tag>"')
142+
143+
args = parser.parse_args()
144+
data.update(vars(args))
145+
146+
if args.tag and not args.buildcommand:
147+
args.buildcommand="build.%s" % args.tag
148+
149+
if args.buildcommand:
150+
data['buildcommand'] = args.buildcommand
151+
elif 'BUILD' in os.environ:
152+
data['buildcommand'] = os.environ['BUILD']
153+
else:
154+
data['buildcommand'] = 'make -j4 -s'
155+
156+
targets = [ 'dbs',
157+
'callgraph',
158+
'gcTypes',
159+
'gcFunctions',
160+
'gcFunctions_list',
161+
'suppressedFunctions_list',
162+
'allFunctions',
163+
'hazards' ]
164+
165+
if args.list:
166+
for target in targets:
167+
command, outfilename = JOBS[target]
168+
if outfilename:
169+
print("%s -> %s" % (target, outfilename))
170+
else:
171+
print(target)
172+
sys.exit(0)
173+
174+
for target in targets:
175+
command, outfilename = JOBS[target]
176+
data[target] = outfilename
177+
178+
if args.target:
179+
targets = targets[targets.index(args.target):]
180+
181+
for target in targets:
182+
run_job(target, data)

js/src/devtools/rootAnalysis/analyzeRoots.js

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,33 @@ load('utility.js');
66
load('annotations.js');
77
load('suppressedPoints.js');
88

9-
var sourceRoot = null;
9+
var sourceRoot = (environment['SOURCE_ROOT'] || '') + '/'
1010

1111
var functionName;
1212
var functionBodies;
1313

1414
if (typeof arguments[0] != 'string' || typeof arguments[1] != 'string')
15-
throw "Usage: analyzeRoots.js <gcFunctions.txt> <gcTypes.txt> [start end [tmpfile]]";
15+
throw "Usage: analyzeRoots.js <gcFunctions.lst> <suppressedFunctions.lst> <gcTypes.txt> [start end [tmpfile]]";
1616

1717
var gcFunctionsFile = arguments[0];
18-
var gcTypesFile = arguments[1];
19-
var batch = arguments[2]|0;
20-
var numBatches = (arguments[3]|0) || 1;
21-
var tmpfile = arguments[4] || "tmp.txt";
18+
var suppressedFunctionsFile = arguments[1];
19+
var gcTypesFile = arguments[2];
20+
var batch = arguments[3]|0;
21+
var numBatches = (arguments[4]|0) || 1;
22+
var tmpfile = arguments[5] || "tmp.txt";
2223

2324
var gcFunctions = {};
24-
var suppressedFunctions = {};
25-
assert(!system("grep 'GC Function' " + gcFunctionsFile + " > tmp.txt"));
26-
var text = snarf("tmp.txt").split('\n');
25+
var text = snarf("gcFunctions.lst").split('\n');
2726
assert(text.pop().length == 0);
2827
for (var line of text) {
29-
match = /GC Function: (.*)/.exec(line);
30-
gcFunctions[match[1]] = true;
28+
gcFunctions[line] = true;
3129
}
32-
assert(!system("grep 'Suppressed Function' " + arguments[0] + " > tmp.txt"));
33-
text = snarf("tmp.txt").split('\n');
30+
31+
var suppressedFunctions = {};
32+
var text = snarf("suppressedFunctions.lst").split('\n');
3433
assert(text.pop().length == 0);
3534
for (var line of text) {
36-
match = /Suppressed Function: (.*)/.exec(line);
37-
suppressedFunctions[match[1]] = true;
35+
suppressedFunctions[line] = true;
3836
}
3937
text = null;
4038

@@ -494,22 +492,6 @@ var each = Math.floor(N/numBatches);
494492
var start = minStream + each * (batch - 1);
495493
var end = Math.min(minStream + each * batch - 1, maxStream);
496494

497-
// Find the source tree
498-
for (let nameIndex = minStream; nameIndex <= maxStream; nameIndex++) {
499-
var name = xdb.read_key(nameIndex);
500-
functionName = name.readString();
501-
var data = xdb.read_entry(name);
502-
functionBodies = JSON.parse(data.readString());
503-
let filename = functionBodies[0].Location[0].CacheString;
504-
let match = /(.*)\bjs\/src\//.exec(filename);
505-
if (match) {
506-
sourceRoot = match[1];
507-
printErr("sourceRoot = " + sourceRoot);
508-
break;
509-
}
510-
}
511-
assert(typeof(sourceRoot) == "string");
512-
513495
for (var nameIndex = start; nameIndex <= end; nameIndex++) {
514496
var name = xdb.read_key(nameIndex);
515497
functionName = name.readString();

js/src/devtools/rootAnalysis/annotations.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ var ignoreIndirectCalls = {
99
"__conv" : true,
1010
"__convf" : true,
1111
"prerrortable.c:callback_newtable" : true,
12+
"mozalloc_oom.cpp:void (* gAbortHandler)(size_t)" : true,
13+
"JSObject* js::GetWeakmapKeyDelegate(JSObject*)" : true, // FIXME: mark with AutoAssertNoGC instead
1214
};
1315

16+
1417
function indirectCallCannotGC(caller, name)
1518
{
1619
if (name in ignoreIndirectCalls)
@@ -26,10 +29,6 @@ function indirectCallCannotGC(caller, name)
2629
if (/CallDestroyScriptHook/.test(caller))
2730
return true;
2831

29-
// hooks called deep inside utility libraries.
30-
if (name == "_malloc_message")
31-
return true;
32-
3332
return false;
3433
}
3534

@@ -43,13 +42,15 @@ var ignoreClasses = {
4342
"PRIOMethods": true,
4443
"XPCOMFunctions" : true, // I'm a little unsure of this one
4544
"_MD_IOVector" : true,
46-
"PRIOMethods" : true,
4745
};
4846

4947
var ignoreCallees = {
5048
"js::Class.trace" : true,
5149
"js::Class.finalize" : true,
5250
"JSRuntime.destroyPrincipals" : true,
51+
"nsISupports.AddRef" : true,
52+
"nsISupports.Release" : true, // makes me a bit nervous; this is a bug but can happen
53+
"nsAXPCNativeCallContext.GetJSContext" : true,
5354
};
5455

5556
function fieldCallCannotGC(csu, fullfield)
@@ -92,18 +93,21 @@ function ignoreEdgeUse(edge, variable)
9293
return false;
9394
}
9495

95-
var ignoreFunctions = [
96-
"ptio.c:pt_MapError",
97-
"PR_ExplodeTime",
98-
"PR_ErrorInstallTable"
99-
];
96+
var ignoreFunctions = {
97+
"ptio.c:pt_MapError" : true,
98+
"PR_ExplodeTime" : true,
99+
"PR_ErrorInstallTable" : true,
100+
"PR_SetThreadPrivate" : true
101+
};
100102

101103
function ignoreGCFunction(fun)
102104
{
103-
for (var i = 0; i < ignoreFunctions.length; i++) {
104-
if (fun == ignoreFunctions[i])
105-
return true;
106-
}
105+
if (fun in ignoreFunctions)
106+
return true;
107+
108+
// Templatized function
109+
if (fun.indexOf("void nsCOMPtr<T>::Assert_NoQueryNeeded()") >= 0)
110+
return true;
107111

108112
// XXX modify refillFreeList<NoGC> to not need data flow analysis to understand it cannot GC.
109113
if (/refillFreeList/.test(fun) && /\(js::AllowGC\)0u/.test(fun))

js/src/devtools/rootAnalysis/gen-hazards.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ set -e
55
JOBS="$1"
66

77
for j in $(seq $JOBS); do
8-
env PATH=$PATH:$SIXGILL/bin XDB=$SIXGILL/bin/xdb.so $JS $ANALYZE gcFunctions.txt gcTypes.txt $j $JOBS tmp.$j > rootingHazards.$j &
8+
env PATH=$PATH:$SIXGILL/bin XDB=$SIXGILL/bin/xdb.so $JS $ANALYZE gcFunctions.lst suppressedFunctions.lst gcTypes.txt $j $JOBS tmp.$j > rootingHazards.$j &
99
done
1010

1111
wait

0 commit comments

Comments
 (0)