Bug 1593728 Part 2: [devtools] Add a simple log message param and function to a conso...
[gecko.git] / config / check_vanilla_allocations.py
blob64e1c611b73cfbd27ca1cc540d772edb5983c0b1
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 # from within SpiderMonkey itself (e.g. malloc(), free()) is a bug. Calls from
14 # within mozglue and non-SpiderMonkey locations are fine; there is a list of
15 # exceptions that can be added to as the need arises.
17 # This script checks for the presence of such disallowed vanilla
18 # allocation/free function in SpiderMonkey when it's built as a library. It
19 # relies on |nm| from the GNU binutils, and so only works on Linux, but one
20 # platform is good enough to catch almost all violations.
22 # This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in
23 # which the default definitions of js_malloc et al (in Utility.h) -- which call
24 # malloc et al -- are replaced with empty definitions. This is because the
25 # presence and possible inlining of the default js_malloc et al can cause
26 # malloc/calloc/realloc/free calls show up in unpredictable places.
28 # Unfortunately, that configuration cannot be tested on Mozilla's standard
29 # testing infrastructure. Instead, by default this script only tests that none
30 # of the other vanilla allocation/free functions (operator new, memalign, etc)
31 # are present. If given the --aggressive flag, it will also check for
32 # malloc/calloc/realloc/free.
34 # Note: We don't check for |operator delete| and |operator delete[]|. These
35 # can be present somehow due to virtual destructors, but this is not too
36 # because vanilla delete/delete[] calls don't make sense without corresponding
37 # vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's
38 # mismatched alloc/free checking.
39 # ----------------------------------------------------------------------------
41 from __future__ import absolute_import, print_function, unicode_literals
43 import argparse
44 import re
45 import subprocess
46 import sys
47 import buildconfig
49 # The obvious way to implement this script is to search for occurrences of
50 # malloc et al, succeed if none are found, and fail is some are found.
51 # However, "none are found" does not necessarily mean "none are present" --
52 # this script could be buggy. (Or the output format of |nm| might change in
53 # the future.)
55 # So util/Utility.cpp deliberately contains a (never-called) function that
56 # contains a single use of all the vanilla allocation/free functions. And this
57 # script fails if it (a) finds uses of those functions in files other than
58 # util/Utility.cpp, *or* (b) fails to find them in util/Utility.cpp.
60 # Tracks overall success of the test.
61 has_failed = False
64 def fail(msg):
65 print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg)
66 global has_failed
67 has_failed = True
70 def main():
71 parser = argparse.ArgumentParser()
72 parser.add_argument('--aggressive', action='store_true',
73 help='also check for malloc, calloc, realloc and free')
74 parser.add_argument('file', type=str,
75 help='name of the file to check')
76 args = parser.parse_args()
78 # Run |nm|. Options:
79 # -u: show only undefined symbols
80 # -C: demangle symbol names
81 # -A: show an object filename for each undefined symbol
82 nm = buildconfig.substs.get('NM') or 'nm'
83 cmd = [nm, '-u', '-C', '-A', args.file]
84 lines = subprocess.check_output(cmd, universal_newlines=True,
85 stderr=subprocess.PIPE).split('\n')
87 # alloc_fns contains all the vanilla allocation/free functions that we look
88 # for. Regexp chars are escaped appropriately.
90 alloc_fns = [
91 # Matches |operator new(unsigned T)|, where |T| is |int| or |long|.
92 r'operator new\(unsigned',
94 # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|.
95 r'operator new\[\]\(unsigned',
97 r'memalign',
98 # These three aren't available on all Linux configurations.
99 # r'posix_memalign',
100 # r'aligned_alloc',
101 # r'valloc',
104 if args.aggressive:
105 alloc_fns += [
106 r'malloc',
107 r'calloc',
108 r'realloc',
109 r'free',
110 r'strdup'
113 # This is like alloc_fns, but regexp chars are not escaped.
114 alloc_fns_unescaped = [fn.replace('\\', '') for fn in alloc_fns]
116 # This regexp matches the relevant lines in the output of |nm|, which look
117 # like the following.
119 # js/src/libjs_static.a:Utility.o: U malloc
121 alloc_fns_re = r'([^:/ ]+):\s+U (' + r'|'.join(alloc_fns) + r')'
123 # This tracks which allocation/free functions have been seen in
124 # util/Utility.cpp.
125 util_Utility_cpp = set([])
127 # Would it be helpful to emit detailed line number information after a failure?
128 emit_line_info = False
130 for line in lines:
131 m = re.search(alloc_fns_re, line)
132 if m is None:
133 continue
135 filename = m.group(1)
137 # The stdc++compat library has an implicit call to operator new in
138 # thread::_M_start_thread.
139 if 'stdc++compat' in filename:
140 continue
142 # The memory allocator code contains calls to memalign. These are ok, so
143 # we whitelist them.
144 if "_memory_" in filename:
145 continue
147 # Ignore the fuzzing code imported from m-c
148 if "Fuzzer" in filename:
149 continue
151 # Ignore the profiling pseudo-stack, since it needs to run even when
152 # SpiderMonkey's allocator isn't initialized.
153 if "ProfilingStack" in filename:
154 continue
156 # Ignore implicit call to operator new in std::condition_variable_any.
158 # From intl/icu/source/common/umutex.h:
159 # On Linux, the default constructor of std::condition_variable_any
160 # produces an in-line reference to global operator new(), [...].
161 if filename == 'umutex.o':
162 continue
164 # Ignore allocations from decimal conversion functions inside mozglue.
165 if filename == 'Decimal.o':
166 continue
168 fn = m.group(2)
169 if filename == 'Utility.o':
170 util_Utility_cpp.add(fn)
171 else:
172 # An allocation is present in a non-special file. Fail!
173 fail("'" + fn + "' present in " + filename)
174 # Try to give more precise information about the offending code.
175 emit_line_info = True
177 # Check that all functions we expect are used in util/Utility.cpp. (This
178 # will fail if the function-detection code breaks at any point.)
179 for fn in alloc_fns_unescaped:
180 if fn not in util_Utility_cpp:
181 fail("'" + fn + "' isn't used as expected in util/Utility.cpp")
182 else:
183 util_Utility_cpp.remove(fn)
185 # This should never happen, but check just in case.
186 if util_Utility_cpp:
187 fail('unexpected allocation fns used in util/Utility.cpp: ' +
188 ', '.join(util_Utility_cpp))
190 # If we found any improper references to allocation functions, try to use
191 # DWARF debug info to get more accurate line number information about the
192 # bad calls. This is a lot slower than 'nm -A', and it is not always
193 # precise when building with --enable-optimized.
194 if emit_line_info:
195 print('check_vanilla_allocations.py: Source lines with allocation calls:')
196 print('check_vanilla_allocations.py: Accurate in unoptimized builds; '
197 'util/Utility.cpp expected.')
199 # Run |nm|. Options:
200 # -u: show only undefined symbols
201 # -C: demangle symbol names
202 # -l: show line number information for each undefined symbol
203 cmd = ['nm', '-u', '-C', '-l', args.file]
204 lines = subprocess.check_output(cmd, universal_newlines=True,
205 stderr=subprocess.PIPE).split('\n')
207 # This regexp matches the relevant lines in the output of |nm -l|,
208 # which look like the following.
210 # U malloc util/Utility.cpp:117
212 alloc_lines_re = r'U ((' + r'|'.join(alloc_fns) + r').*)\s+(\S+:\d+)$'
214 for line in lines:
215 m = re.search(alloc_lines_re, line)
216 if m:
217 print('check_vanilla_allocations.py:',
218 m.group(1), 'called at', m.group(3))
220 if has_failed:
221 sys.exit(1)
223 print('TEST-PASS | check_vanilla_allocations.py | ok')
224 sys.exit(0)
227 if __name__ == '__main__':
228 main()