|
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/. |
|
5 |
|
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 #---------------------------------------------------------------------------- |
|
38 |
|
39 from __future__ import print_function |
|
40 |
|
41 import argparse |
|
42 import re |
|
43 import subprocess |
|
44 import sys |
|
45 |
|
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. |
|
56 |
|
57 # Tracks overall success of the test. |
|
58 has_failed = False |
|
59 |
|
60 |
|
61 def fail(msg): |
|
62 print('TEST-UNEXPECTED-FAIL | check_vanilla_allocations.py |', msg) |
|
63 global has_failed |
|
64 has_failed = True |
|
65 |
|
66 |
|
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() |
|
74 |
|
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') |
|
82 |
|
83 # alloc_fns contains all the vanilla allocation/free functions that we look |
|
84 # for. Regexp chars are escaped appropriately. |
|
85 |
|
86 alloc_fns = [ |
|
87 # Matches |operator new(unsigned T)|, where |T| is |int| or |long|. |
|
88 r'operator new\(unsigned', |
|
89 |
|
90 # Matches |operator new[](unsigned T)|, where |T| is |int| or |long|. |
|
91 r'operator new\[\]\(unsigned', |
|
92 |
|
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 ] |
|
100 |
|
101 if args.aggressive: |
|
102 alloc_fns += [ |
|
103 r'malloc', |
|
104 r'calloc', |
|
105 r'realloc', |
|
106 r'free' |
|
107 ] |
|
108 |
|
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] |
|
111 |
|
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+)$' |
|
118 |
|
119 # This tracks which allocation/free functions have been seen in jsutil.cpp. |
|
120 jsutil_cpp = set([]) |
|
121 |
|
122 for line in lines: |
|
123 m = re.search(alloc_fns_re, line) |
|
124 if m is None: |
|
125 continue |
|
126 |
|
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) |
|
135 |
|
136 |
|
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) |
|
144 |
|
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)) |
|
149 |
|
150 if has_failed: |
|
151 sys.exit(1) |
|
152 |
|
153 print('TEST-PASS | check_vanilla_allocations.py | ok') |
|
154 sys.exit(0) |
|
155 |
|
156 |
|
157 if __name__ == '__main__': |
|
158 main() |
|
159 |