michael@0: # vim: set ts=8 sts=4 et sw=4 tw=79: michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: #---------------------------------------------------------------------------- michael@0: # All heap allocations in SpiderMonkey must go through js_malloc, js_calloc, michael@0: # js_realloc, and js_free. This is so that any embedder who uses a custom michael@0: # allocator (by defining JS_USE_CUSTOM_ALLOCATOR) will see all heap allocation michael@0: # go through that custom allocator. michael@0: # michael@0: # Therefore, the presence of any calls to "vanilla" allocation/free functions michael@0: # (e.g. malloc(), free()) is a bug. michael@0: # michael@0: # This script checks for the presence of such disallowed vanilla michael@0: # allocation/free function in SpiderMonkey when it's built as a library. It michael@0: # relies on |nm| from the GNU binutils, and so only works on Linux, but one michael@0: # platform is good enough to catch almost all violations. michael@0: # michael@0: # This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in michael@0: # which the default definitions of js_malloc et al (in Utility.h) -- which call michael@0: # malloc et al -- are replaced with empty definitions. This is because the michael@0: # presence and possible inlining of the default js_malloc et al can cause michael@0: # malloc/calloc/realloc/free calls show up in unpredictable places. michael@0: # michael@0: # Unfortunately, that configuration cannot be tested on Mozilla's standard michael@0: # testing infrastructure. Instead, by default this script only tests that none michael@0: # of the other vanilla allocation/free functions (operator new, memalign, etc) michael@0: # are present. If given the --aggressive flag, it will also check for michael@0: # malloc/calloc/realloc/free. michael@0: # michael@0: # Note: We don't check for |operator delete| and |operator delete[]|. These michael@0: # can be present somehow due to virtual destructors, but this is not too michael@0: # because vanilla delete/delete[] calls don't make sense without corresponding michael@0: # vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's michael@0: # mismatched alloc/free checking. michael@0: #---------------------------------------------------------------------------- michael@0: michael@0: from __future__ import print_function michael@0: michael@0: import argparse michael@0: import re michael@0: import subprocess michael@0: import sys michael@0: michael@0: # The obvious way to implement this script is to search for occurrences of michael@0: # malloc et al, succeed if none are found, and fail is some are found. michael@0: # However, "none are found" does not necessarily mean "none are present" -- michael@0: # this script could be buggy. (Or the output format of |nm| might change in michael@0: # the future.) michael@0: # michael@0: # So jsutil.cpp deliberately contains a (never-called) function that contains a michael@0: # single use of all the vanilla allocation/free functions. And this script michael@0: # fails if it (a) finds uses of those functions in files other than jsutil.cpp, michael@0: # *or* (b) fails to find them in jsutil.cpp. michael@0: michael@0: # Tracks overall success of the test. michael@0: has_failed = False michael@0: michael@0: michael@0: def fail(msg): michael@0: print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg) michael@0: global has_failed michael@0: has_failed = True michael@0: michael@0: michael@0: def main(): michael@0: parser = argparse.ArgumentParser() michael@0: parser.add_argument('--aggressive', action='store_true', michael@0: help='also check for malloc, calloc, realloc and free') michael@0: parser.add_argument('file', type=str, michael@0: help='name of the file to check') michael@0: args = parser.parse_args() michael@0: michael@0: # Run |nm|. Options: michael@0: # -u: show only undefined symbols michael@0: # -C: demangle symbol names michael@0: # -l: show a filename and line number for each undefined symbol michael@0: cmd = ['nm', '-u', '-C', '-l', args.file] michael@0: lines = subprocess.check_output(cmd, universal_newlines=True, michael@0: stderr=subprocess.PIPE).split('\n') michael@0: michael@0: # alloc_fns contains all the vanilla allocation/free functions that we look michael@0: # for. Regexp chars are escaped appropriately. michael@0: michael@0: alloc_fns = [ michael@0: # Matches |operator new(unsigned T)|, where |T| is |int| or |long|. michael@0: r'operator new\(unsigned', michael@0: michael@0: # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|. michael@0: r'operator new\[\]\(unsigned', michael@0: michael@0: r'memalign', michael@0: # These two aren't available on all Linux configurations. michael@0: #r'posix_memalign', michael@0: #r'aligned_alloc', michael@0: r'valloc', michael@0: r'strdup' michael@0: ] michael@0: michael@0: if args.aggressive: michael@0: alloc_fns += [ michael@0: r'malloc', michael@0: r'calloc', michael@0: r'realloc', michael@0: r'free' michael@0: ] michael@0: michael@0: # This is like alloc_fns, but regexp chars are not escaped. michael@0: alloc_fns_unescaped = [fn.translate(None, r'\\') for fn in alloc_fns] michael@0: michael@0: # This regexp matches the relevant lines in the output of |nm|, which look michael@0: # like the following. michael@0: # michael@0: # U malloc /path/to/objdir/dist/include/js/Utility.h:142 michael@0: # michael@0: alloc_fns_re = r'U (' + r'|'.join(alloc_fns) + r').*\/([\w\.]+):(\d+)$' michael@0: michael@0: # This tracks which allocation/free functions have been seen in jsutil.cpp. michael@0: jsutil_cpp = set([]) michael@0: michael@0: for line in lines: michael@0: m = re.search(alloc_fns_re, line) michael@0: if m is None: michael@0: continue michael@0: michael@0: fn = m.group(1) michael@0: filename = m.group(2) michael@0: linenum = m.group(3) michael@0: if filename == 'jsutil.cpp': michael@0: jsutil_cpp.add(fn) michael@0: else: michael@0: # An allocation is present in a non-special file. Fail! michael@0: fail("'" + fn + "' present at " + filename + ':' + linenum) michael@0: michael@0: michael@0: # Check that all functions we expect are used in jsutil.cpp. (This will michael@0: # fail if the function-detection code breaks at any point.) michael@0: for fn in alloc_fns_unescaped: michael@0: if fn not in jsutil_cpp: michael@0: fail("'" + fn + "' isn't used as expected in jsutil.cpp") michael@0: else: michael@0: jsutil_cpp.remove(fn) michael@0: michael@0: # This should never happen, but check just in case. michael@0: if jsutil_cpp: michael@0: fail('unexpected allocation fns used in jsutil.cpp: ' + michael@0: ', '.join(jsutil_cpp)) michael@0: michael@0: if has_failed: michael@0: sys.exit(1) michael@0: michael@0: print('TEST-PASS | check_vanilla_allocations.py | ok') michael@0: sys.exit(0) michael@0: michael@0: michael@0: if __name__ == '__main__': michael@0: main() michael@0: