1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/config/check_vanilla_allocations.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,159 @@ 1.4 +# vim: set ts=8 sts=4 et sw=4 tw=79: 1.5 +# This Source Code Form is subject to the terms of the Mozilla Public 1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.8 + 1.9 +#---------------------------------------------------------------------------- 1.10 +# All heap allocations in SpiderMonkey must go through js_malloc, js_calloc, 1.11 +# js_realloc, and js_free. This is so that any embedder who uses a custom 1.12 +# allocator (by defining JS_USE_CUSTOM_ALLOCATOR) will see all heap allocation 1.13 +# go through that custom allocator. 1.14 +# 1.15 +# Therefore, the presence of any calls to "vanilla" allocation/free functions 1.16 +# (e.g. malloc(), free()) is a bug. 1.17 +# 1.18 +# This script checks for the presence of such disallowed vanilla 1.19 +# allocation/free function in SpiderMonkey when it's built as a library. It 1.20 +# relies on |nm| from the GNU binutils, and so only works on Linux, but one 1.21 +# platform is good enough to catch almost all violations. 1.22 +# 1.23 +# This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in 1.24 +# which the default definitions of js_malloc et al (in Utility.h) -- which call 1.25 +# malloc et al -- are replaced with empty definitions. This is because the 1.26 +# presence and possible inlining of the default js_malloc et al can cause 1.27 +# malloc/calloc/realloc/free calls show up in unpredictable places. 1.28 +# 1.29 +# Unfortunately, that configuration cannot be tested on Mozilla's standard 1.30 +# testing infrastructure. Instead, by default this script only tests that none 1.31 +# of the other vanilla allocation/free functions (operator new, memalign, etc) 1.32 +# are present. If given the --aggressive flag, it will also check for 1.33 +# malloc/calloc/realloc/free. 1.34 +# 1.35 +# Note: We don't check for |operator delete| and |operator delete[]|. These 1.36 +# can be present somehow due to virtual destructors, but this is not too 1.37 +# because vanilla delete/delete[] calls don't make sense without corresponding 1.38 +# vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's 1.39 +# mismatched alloc/free checking. 1.40 +#---------------------------------------------------------------------------- 1.41 + 1.42 +from __future__ import print_function 1.43 + 1.44 +import argparse 1.45 +import re 1.46 +import subprocess 1.47 +import sys 1.48 + 1.49 +# The obvious way to implement this script is to search for occurrences of 1.50 +# malloc et al, succeed if none are found, and fail is some are found. 1.51 +# However, "none are found" does not necessarily mean "none are present" -- 1.52 +# this script could be buggy. (Or the output format of |nm| might change in 1.53 +# the future.) 1.54 +# 1.55 +# So jsutil.cpp deliberately contains a (never-called) function that contains a 1.56 +# single use of all the vanilla allocation/free functions. And this script 1.57 +# fails if it (a) finds uses of those functions in files other than jsutil.cpp, 1.58 +# *or* (b) fails to find them in jsutil.cpp. 1.59 + 1.60 +# Tracks overall success of the test. 1.61 +has_failed = False 1.62 + 1.63 + 1.64 +def fail(msg): 1.65 + print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg) 1.66 + global has_failed 1.67 + has_failed = True 1.68 + 1.69 + 1.70 +def main(): 1.71 + parser = argparse.ArgumentParser() 1.72 + parser.add_argument('--aggressive', action='store_true', 1.73 + help='also check for malloc, calloc, realloc and free') 1.74 + parser.add_argument('file', type=str, 1.75 + help='name of the file to check') 1.76 + args = parser.parse_args() 1.77 + 1.78 + # Run |nm|. Options: 1.79 + # -u: show only undefined symbols 1.80 + # -C: demangle symbol names 1.81 + # -l: show a filename and line number for each undefined symbol 1.82 + cmd = ['nm', '-u', '-C', '-l', args.file] 1.83 + lines = subprocess.check_output(cmd, universal_newlines=True, 1.84 + stderr=subprocess.PIPE).split('\n') 1.85 + 1.86 + # alloc_fns contains all the vanilla allocation/free functions that we look 1.87 + # for. Regexp chars are escaped appropriately. 1.88 + 1.89 + alloc_fns = [ 1.90 + # Matches |operator new(unsigned T)|, where |T| is |int| or |long|. 1.91 + r'operator new\(unsigned', 1.92 + 1.93 + # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|. 1.94 + r'operator new\[\]\(unsigned', 1.95 + 1.96 + r'memalign', 1.97 + # These two aren't available on all Linux configurations. 1.98 + #r'posix_memalign', 1.99 + #r'aligned_alloc', 1.100 + r'valloc', 1.101 + r'strdup' 1.102 + ] 1.103 + 1.104 + if args.aggressive: 1.105 + alloc_fns += [ 1.106 + r'malloc', 1.107 + r'calloc', 1.108 + r'realloc', 1.109 + r'free' 1.110 + ] 1.111 + 1.112 + # This is like alloc_fns, but regexp chars are not escaped. 1.113 + alloc_fns_unescaped = [fn.translate(None, r'\\') for fn in alloc_fns] 1.114 + 1.115 + # This regexp matches the relevant lines in the output of |nm|, which look 1.116 + # like the following. 1.117 + # 1.118 + # U malloc /path/to/objdir/dist/include/js/Utility.h:142 1.119 + # 1.120 + alloc_fns_re = r'U (' + r'|'.join(alloc_fns) + r').*\/([\w\.]+):(\d+)$' 1.121 + 1.122 + # This tracks which allocation/free functions have been seen in jsutil.cpp. 1.123 + jsutil_cpp = set([]) 1.124 + 1.125 + for line in lines: 1.126 + m = re.search(alloc_fns_re, line) 1.127 + if m is None: 1.128 + continue 1.129 + 1.130 + fn = m.group(1) 1.131 + filename = m.group(2) 1.132 + linenum = m.group(3) 1.133 + if filename == 'jsutil.cpp': 1.134 + jsutil_cpp.add(fn) 1.135 + else: 1.136 + # An allocation is present in a non-special file. Fail! 1.137 + fail("'" + fn + "' present at " + filename + ':' + linenum) 1.138 + 1.139 + 1.140 + # Check that all functions we expect are used in jsutil.cpp. (This will 1.141 + # fail if the function-detection code breaks at any point.) 1.142 + for fn in alloc_fns_unescaped: 1.143 + if fn not in jsutil_cpp: 1.144 + fail("'" + fn + "' isn't used as expected in jsutil.cpp") 1.145 + else: 1.146 + jsutil_cpp.remove(fn) 1.147 + 1.148 + # This should never happen, but check just in case. 1.149 + if jsutil_cpp: 1.150 + fail('unexpected allocation fns used in jsutil.cpp: ' + 1.151 + ', '.join(jsutil_cpp)) 1.152 + 1.153 + if has_failed: 1.154 + sys.exit(1) 1.155 + 1.156 + print('TEST-PASS | check_vanilla_allocations.py | ok') 1.157 + sys.exit(0) 1.158 + 1.159 + 1.160 +if __name__ == '__main__': 1.161 + main() 1.162 +