config/check_vanilla_allocations.py

changeset 0
6474c204b198
     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 +

mercurial