1 # vim: set ts=8 sts=4 et sw=4 tw=79:
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 # ----------------------------------------------------------------------------
7 # All heap allocations in SpiderMonkey must go through js_malloc, js_calloc,
8 # js_realloc, and js_free. This is so that any embedder who uses a custom
9 # allocator (by defining JS_USE_CUSTOM_ALLOCATOR) will see all heap allocation
10 # go through that custom allocator.
12 # Therefore, the presence of any calls to "vanilla" allocation/free functions
13 # (e.g. malloc(), free()) is a bug.
15 # This script checks for the presence of such disallowed vanilla
16 # allocation/free function in SpiderMonkey when it's built as a library. It
17 # relies on |nm| from the GNU binutils, and so only works on Linux, but one
18 # platform is good enough to catch almost all violations.
20 # This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in
21 # which the default definitions of js_malloc et al (in Utility.h) -- which call
22 # malloc et al -- are replaced with empty definitions. This is because the
23 # presence and possible inlining of the default js_malloc et al can cause
24 # malloc/calloc/realloc/free calls show up in unpredictable places.
26 # Unfortunately, that configuration cannot be tested on Mozilla's standard
27 # testing infrastructure. Instead, by default this script only tests that none
28 # of the other vanilla allocation/free functions (operator new, memalign, etc)
29 # are present. If given the --aggressive flag, it will also check for
30 # malloc/calloc/realloc/free.
32 # Note: We don't check for |operator delete| and |operator delete[]|. These
33 # can be present somehow due to virtual destructors, but this is not too
34 # because vanilla delete/delete[] calls don't make sense without corresponding
35 # vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's
36 # mismatched alloc/free checking.
37 # ----------------------------------------------------------------------------
39 from __future__
import print_function
47 # The obvious way to implement this script is to search for occurrences of
48 # malloc et al, succeed if none are found, and fail is some are found.
49 # However, "none are found" does not necessarily mean "none are present" --
50 # this script could be buggy. (Or the output format of |nm| might change in
53 # So jsutil.cpp deliberately contains a (never-called) function that contains a
54 # single use of all the vanilla allocation/free functions. And this script
55 # fails if it (a) finds uses of those functions in files other than jsutil.cpp,
56 # *or* (b) fails to find them in jsutil.cpp.
58 # Tracks overall success of the test.
63 print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg
)
69 parser
= argparse
.ArgumentParser()
70 parser
.add_argument('--aggressive', action
='store_true',
71 help='also check for malloc, calloc, realloc and free')
72 parser
.add_argument('file', type=str,
73 help='name of the file to check')
74 args
= parser
.parse_args()
77 # -u: show only undefined symbols
78 # -C: demangle symbol names
79 # -A: show an object filename for each undefined symbol
80 nm
= buildconfig
.substs
.get('NM') or 'nm'
81 cmd
= [nm
, '-u', '-C', '-A', args
.file]
82 lines
= subprocess
.check_output(cmd
, universal_newlines
=True,
83 stderr
=subprocess
.PIPE
).split('\n')
85 # alloc_fns contains all the vanilla allocation/free functions that we look
86 # for. Regexp chars are escaped appropriately.
89 # Matches |operator new(unsigned T)|, where |T| is |int| or |long|.
90 r
'operator new\(unsigned',
92 # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|.
93 r
'operator new\[\]\(unsigned',
96 # These three aren't available on all Linux configurations.
111 # This is like alloc_fns, but regexp chars are not escaped.
112 alloc_fns_unescaped
= [fn
.translate(None, r
'\\') for fn
in alloc_fns
]
114 # This regexp matches the relevant lines in the output of |nm|, which look
115 # like the following.
117 # js/src/libjs_static.a:jsutil.o: U malloc
119 alloc_fns_re
= r
'([^:/ ]+):\s+U (' + r
'|'.join(alloc_fns
) + r
')'
121 # This tracks which allocation/free functions have been seen in jsutil.cpp.
124 # Would it be helpful to emit detailed line number information after a failure?
125 emit_line_info
= False
128 m
= re
.search(alloc_fns_re
, line
)
132 filename
= m
.group(1)
134 # The stdc++compat library has an implicit call to operator new in
135 # thread::_M_start_thread.
136 if 'stdc++compat' in filename
:
139 # The memory allocator code contains calls to memalign. These are ok, so
141 if "_memory_" in filename
:
144 # Ignore the fuzzing code imported from m-c
145 if "Fuzzer" in filename
:
148 # Ignore the profiling pseudo-stack, since it needs to run even when
149 # SpiderMonkey's allocator isn't initialized.
150 if "ProfilingStack" in filename
:
154 if filename
== 'jsutil.o':
157 # An allocation is present in a non-special file. Fail!
158 fail("'" + fn
+ "' present in " + filename
)
159 # Try to give more precise information about the offending code.
160 emit_line_info
= True
162 # Check that all functions we expect are used in jsutil.cpp. (This will
163 # fail if the function-detection code breaks at any point.)
164 for fn
in alloc_fns_unescaped
:
165 if fn
not in jsutil_cpp
:
166 fail("'" + fn
+ "' isn't used as expected in jsutil.cpp")
168 jsutil_cpp
.remove(fn
)
170 # This should never happen, but check just in case.
172 fail('unexpected allocation fns used in jsutil.cpp: ' +
173 ', '.join(jsutil_cpp
))
175 # If we found any improper references to allocation functions, try to use
176 # DWARF debug info to get more accurate line number information about the
177 # bad calls. This is a lot slower than 'nm -A', and it is not always
178 # precise when building with --enable-optimized.
180 print('check_vanilla_allocations.py: Source lines with allocation calls:')
181 print('check_vanilla_allocations.py: Accurate in unoptimized builds; jsutil.cpp expected.')
184 # -u: show only undefined symbols
185 # -C: demangle symbol names
186 # -l: show line number information for each undefined symbol
187 cmd
= ['nm', '-u', '-C', '-l', args
.file]
188 lines
= subprocess
.check_output(cmd
, universal_newlines
=True,
189 stderr
=subprocess
.PIPE
).split('\n')
191 # This regexp matches the relevant lines in the output of |nm -l|,
192 # which look like the following.
194 # U malloc jsutil.cpp:117
196 alloc_lines_re
= r
'U ((' + r
'|'.join(alloc_fns
) + r
').*)\s+(\S+:\d+)$'
199 m
= re
.search(alloc_lines_re
, line
)
201 print('check_vanilla_allocations.py:',
202 m
.group(1), 'called at', m
.group(3))
207 print('TEST-PASS | check_vanilla_allocations.py | ok')
211 if __name__
== '__main__':