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.

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

mercurial