123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- # 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()
|