config/check_vanilla_allocations.py

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 # vim: set ts=8 sts=4 et sw=4 tw=79:
     2 # This Source Code Form is subject to the terms of the Mozilla Public
     3 # License, v. 2.0. If a copy of the MPL was not distributed with this
     4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6 #----------------------------------------------------------------------------
     7 # All heap allocations in SpiderMonkey must go through js_malloc, js_calloc,
     8 # js_realloc, and js_free.  This is so that any embedder who uses a custom
     9 # allocator (by defining JS_USE_CUSTOM_ALLOCATOR) will see all heap allocation
    10 # go through that custom allocator.
    11 #
    12 # Therefore, the presence of any calls to "vanilla" allocation/free functions
    13 # (e.g. malloc(), free()) is a bug.
    14 #
    15 # This script checks for the presence of such disallowed vanilla
    16 # allocation/free function in SpiderMonkey when it's built as a library.  It
    17 # relies on |nm| from the GNU binutils, and so only works on Linux, but one
    18 # platform is good enough to catch almost all violations.
    19 #
    20 # This checking is only 100% reliable in a JS_USE_CUSTOM_ALLOCATOR build in
    21 # which the default definitions of js_malloc et al (in Utility.h) -- which call
    22 # malloc et al -- are replaced with empty definitions.  This is because the
    23 # presence and possible inlining of the default js_malloc et al can cause
    24 # malloc/calloc/realloc/free calls show up in unpredictable places.
    25 #
    26 # Unfortunately, that configuration cannot be tested on Mozilla's standard
    27 # testing infrastructure.  Instead, by default this script only tests that none
    28 # of the other vanilla allocation/free functions (operator new, memalign, etc)
    29 # are present.  If given the --aggressive flag, it will also check for
    30 # malloc/calloc/realloc/free.
    31 #
    32 # Note:  We don't check for |operator delete| and |operator delete[]|.  These
    33 # can be present somehow due to virtual destructors, but this is not too
    34 # because vanilla delete/delete[] calls don't make sense without corresponding
    35 # vanilla new/new[] calls, and any explicit calls will be caught by Valgrind's
    36 # mismatched alloc/free checking.
    37 #----------------------------------------------------------------------------
    39 from __future__ import print_function
    41 import argparse
    42 import re
    43 import subprocess
    44 import sys
    46 # The obvious way to implement this script is to search for occurrences of
    47 # malloc et al, succeed if none are found, and fail is some are found.
    48 # However, "none are found" does not necessarily mean "none are present" --
    49 # this script could be buggy.  (Or the output format of |nm| might change in
    50 # the future.)
    51 #
    52 # So jsutil.cpp deliberately contains a (never-called) function that contains a
    53 # single use of all the vanilla allocation/free functions.  And this script
    54 # fails if it (a) finds uses of those functions in files other than jsutil.cpp,
    55 # *or* (b) fails to find them in jsutil.cpp.
    57 # Tracks overall success of the test.
    58 has_failed = False
    61 def fail(msg):
    62     print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg)
    63     global has_failed
    64     has_failed = True
    67 def main():
    68     parser = argparse.ArgumentParser()
    69     parser.add_argument('--aggressive', action='store_true',
    70                         help='also check for malloc, calloc, realloc and free')
    71     parser.add_argument('file', type=str,
    72                         help='name of the file to check')
    73     args = parser.parse_args()
    75     # Run |nm|.  Options:
    76     # -u: show only undefined symbols
    77     # -C: demangle symbol names
    78     # -l: show a filename and line number for each undefined symbol
    79     cmd = ['nm', '-u', '-C', '-l', args.file]
    80     lines = subprocess.check_output(cmd, universal_newlines=True,
    81                                     stderr=subprocess.PIPE).split('\n')
    83     # alloc_fns contains all the vanilla allocation/free functions that we look
    84     # for. Regexp chars are escaped appropriately.
    86     alloc_fns = [
    87         # Matches |operator new(unsigned T)|, where |T| is |int| or |long|.
    88         r'operator new\(unsigned',
    90         # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|.
    91         r'operator new\[\]\(unsigned',
    93         r'memalign',
    94         # These two aren't available on all Linux configurations.
    95         #r'posix_memalign',
    96         #r'aligned_alloc',
    97         r'valloc',
    98         r'strdup'
    99     ]
   101     if args.aggressive:
   102         alloc_fns += [
   103             r'malloc',
   104             r'calloc',
   105             r'realloc',
   106             r'free'
   107         ]
   109     # This is like alloc_fns, but regexp chars are not escaped.
   110     alloc_fns_unescaped = [fn.translate(None, r'\\') for fn in alloc_fns]
   112     # This regexp matches the relevant lines in the output of |nm|, which look
   113     # like the following.
   114     #
   115     #   U malloc  /path/to/objdir/dist/include/js/Utility.h:142
   116     #
   117     alloc_fns_re = r'U (' + r'|'.join(alloc_fns) + r').*\/([\w\.]+):(\d+)$'
   119     # This tracks which allocation/free functions have been seen in jsutil.cpp.
   120     jsutil_cpp = set([])
   122     for line in lines:
   123         m = re.search(alloc_fns_re, line)
   124         if m is None:
   125             continue
   127         fn = m.group(1)
   128         filename = m.group(2)
   129         linenum = m.group(3)
   130         if filename == 'jsutil.cpp':
   131             jsutil_cpp.add(fn)
   132         else:
   133             # An allocation is present in a non-special file.  Fail!
   134             fail("'" + fn + "' present at " + filename + ':' + linenum)
   137     # Check that all functions we expect are used in jsutil.cpp.  (This will
   138     # fail if the function-detection code breaks at any point.)
   139     for fn in alloc_fns_unescaped:
   140         if fn not in jsutil_cpp:
   141             fail("'" + fn + "' isn't used as expected in jsutil.cpp")
   142         else:
   143             jsutil_cpp.remove(fn)
   145     # This should never happen, but check just in case.
   146     if jsutil_cpp:
   147         fail('unexpected allocation fns used in jsutil.cpp: ' +
   148              ', '.join(jsutil_cpp))
   150     if has_failed:
   151         sys.exit(1)
   153     print('TEST-PASS | check_vanilla_allocations.py | ok')
   154     sys.exit(0)
   157 if __name__ == '__main__':
   158     main()

mercurial