diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | ad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /config/check_vanilla_allocations.py | |
parent | 15477ed9af4859dacb069040b5d4de600803d3bc (diff) | |
download | aura-central-ad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'config/check_vanilla_allocations.py')
-rw-r--r-- | config/check_vanilla_allocations.py | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/config/check_vanilla_allocations.py b/config/check_vanilla_allocations.py new file mode 100644 index 000000000..2a3be879e --- /dev/null +++ b/config/check_vanilla_allocations.py @@ -0,0 +1,191 @@ +# vim: set ts=8 sts=4 et sw=4 tw=79: +# 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/. + +#---------------------------------------------------------------------------- +# All heap allocations in SpiderMonkey must go through js_malloc, js_calloc, +# js_realloc, and js_free. This is so that any embedder who uses a custom +# allocator (by defining JS_USE_CUSTOM_ALLOCATOR) will see all heap allocation +# go through that custom allocator. +# +# Therefore, the presence of any calls to "vanilla" allocation/free functions +# (e.g. malloc(), free()) is a bug. +# +# This script checks for the presence of such disallowed vanilla +# allocation/free function in SpiderMonkey when it's built as a library. It +# relies on |nm| from the GNU binutils, and so only works on Linux, but one +# platform is good enough to catch almost all violations. +# +# This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in +# which the default definitions of js_malloc et al (in Utility.h) -- which call +# malloc et al -- are replaced with empty definitions. This is because the +# presence and possible inlining of the default js_malloc et al can cause +# malloc/calloc/realloc/free calls show up in unpredictable places. +# +# Unfortunately, that configuration cannot be tested on Mozilla's standard +# testing infrastructure. Instead, by default this script only tests that none +# of the other vanilla allocation/free functions (operator new, memalign, etc) +# are present. If given the --aggressive flag, it will also check for +# malloc/calloc/realloc/free. +# +# Note: We don't check for |operator delete| and |operator delete[]|. These +# can be present somehow due to virtual destructors, but this is not too +# because vanilla delete/delete[] calls don't make sense without corresponding +# vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's +# mismatched alloc/free checking. +#---------------------------------------------------------------------------- + +from __future__ import print_function + +import argparse +import re +import subprocess +import sys + +# The obvious way to implement this script is to search for occurrences of +# malloc et al, succeed if none are found, and fail is some are found. +# However, "none are found" does not necessarily mean "none are present" -- +# this script could be buggy. (Or the output format of |nm| might change in +# the future.) +# +# So jsutil.cpp deliberately contains a (never-called) function that contains a +# single use of all the vanilla allocation/free functions. And this script +# fails if it (a) finds uses of those functions in files other than jsutil.cpp, +# *or* (b) fails to find them in jsutil.cpp. + +# Tracks overall success of the test. +has_failed = False + + +def fail(msg): + print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg) + global has_failed + has_failed = True + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--aggressive', action='store_true', + help='also check for malloc, calloc, realloc and free') + parser.add_argument('file', type=str, + help='name of the file to check') + args = parser.parse_args() + + # Run |nm|. Options: + # -u: show only undefined symbols + # -C: demangle symbol names + # -A: show an object filename for each undefined symbol + cmd = ['nm', '-u', '-C', '-A', args.file] + lines = subprocess.check_output(cmd, universal_newlines=True, + stderr=subprocess.PIPE).split('\n') + + # alloc_fns contains all the vanilla allocation/free functions that we look + # for. Regexp chars are escaped appropriately. + + alloc_fns = [ + # Matches |operator new(unsigned T)|, where |T| is |int| or |long|. + r'operator new\(unsigned', + + # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|. + r'operator new\[\]\(unsigned', + + r'memalign', + # These three aren't available on all Linux configurations. + #r'posix_memalign', + #r'aligned_alloc', + #r'valloc', + ] + + if args.aggressive: + alloc_fns += [ + r'malloc', + r'calloc', + r'realloc', + r'free', + r'strdup' + ] + + # This is like alloc_fns, but regexp chars are not escaped. + alloc_fns_unescaped = [fn.translate(None, r'\\') for fn in alloc_fns] + + # This regexp matches the relevant lines in the output of |nm|, which look + # like the following. + # + # js/src/libjs_static.a:jsutil.o: U malloc + # + alloc_fns_re = r'([^:/ ]+):\s+U (' + r'|'.join(alloc_fns) + r')' + + # This tracks which allocation/free functions have been seen in jsutil.cpp. + jsutil_cpp = set([]) + + # Would it be helpful to emit detailed line number information after a failure? + emit_line_info = False + + for line in lines: + m = re.search(alloc_fns_re, line) + if m is None: + continue + + filename = m.group(1) + fn = m.group(2) + if filename == 'jsutil.o': + jsutil_cpp.add(fn) + else: + # An allocation is present in a non-special file. Fail! + fail("'" + fn + "' present in " + filename) + # Try to give more precise information about the offending code. + emit_line_info = True + + + # Check that all functions we expect are used in jsutil.cpp. (This will + # fail if the function-detection code breaks at any point.) + for fn in alloc_fns_unescaped: + if fn not in jsutil_cpp: + fail("'" + fn + "' isn't used as expected in jsutil.cpp") + else: + jsutil_cpp.remove(fn) + + # This should never happen, but check just in case. + if jsutil_cpp: + fail('unexpected allocation fns used in jsutil.cpp: ' + + ', '.join(jsutil_cpp)) + + # If we found any improper references to allocation functions, try to use + # DWARF debug info to get more accurate line number information about the + # bad calls. This is a lot slower than 'nm -A', and it is not always + # precise when building with --enable-optimized. + if emit_line_info: + print('check_vanilla_allocations.py: Source lines with allocation calls:') + print('check_vanilla_allocations.py: Accurate in unoptimized builds; jsutil.cpp expected.') + + # Run |nm|. Options: + # -u: show only undefined symbols + # -C: demangle symbol names + # -l: show line number information for each undefined symbol + cmd = ['nm', '-u', '-C', '-l', args.file] + lines = subprocess.check_output(cmd, universal_newlines=True, + stderr=subprocess.PIPE).split('\n') + + # This regexp matches the relevant lines in the output of |nm -l|, + # which look like the following. + # + # U malloc jsutil.cpp:117 + # + alloc_lines_re = r'U ((' + r'|'.join(alloc_fns) + r').*)\s+(\S+:\d+)$' + + for line in lines: + m = re.search(alloc_lines_re, line) + if m: + print('check_vanilla_allocations.py:', m.group(1), 'called at', m.group(3)) + + if has_failed: + sys.exit(1) + + print('TEST-PASS | check_vanilla_allocations.py | ok') + sys.exit(0) + + +if __name__ == '__main__': + main() + |