Tue, 06 Jan 2015 21:39:09 +0100
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=99: |
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 | # This script checks various aspects of SpiderMonkey code style. The current checks are as |
michael@0 | 8 | # follows. |
michael@0 | 9 | # |
michael@0 | 10 | # We check the following things in headers. |
michael@0 | 11 | # |
michael@0 | 12 | # - No cyclic dependencies. |
michael@0 | 13 | # |
michael@0 | 14 | # - No normal header should #include a inlines.h/-inl.h file. |
michael@0 | 15 | # |
michael@0 | 16 | # - #ifndef wrappers should have the right form. (XXX: not yet implemented) |
michael@0 | 17 | # - Every header file should have one. |
michael@0 | 18 | # - The guard name used should be appropriate for the filename. |
michael@0 | 19 | # |
michael@0 | 20 | # We check the following things in all files. |
michael@0 | 21 | # |
michael@0 | 22 | # - #includes should have full paths, e.g. "jit/Ion.h", not "Ion.h". |
michael@0 | 23 | # |
michael@0 | 24 | # - #includes should use the appropriate form for system headers (<...>) and |
michael@0 | 25 | # local headers ("..."). |
michael@0 | 26 | # |
michael@0 | 27 | # - #includes should be ordered correctly. |
michael@0 | 28 | # - Each one should be in the correct section. |
michael@0 | 29 | # - Alphabetical order should be used within sections. |
michael@0 | 30 | # - Sections should be in the right order. |
michael@0 | 31 | # Note that the presence of #if/#endif blocks complicates things, to the |
michael@0 | 32 | # point that it's not always clear where a conditionally-compiled #include |
michael@0 | 33 | # statement should go, even to a human. Therefore, we check the #include |
michael@0 | 34 | # statements within each #if/#endif block (including nested ones) in |
michael@0 | 35 | # isolation, but don't try to do any order checking between such blocks. |
michael@0 | 36 | #---------------------------------------------------------------------------- |
michael@0 | 37 | |
michael@0 | 38 | from __future__ import print_function |
michael@0 | 39 | |
michael@0 | 40 | import difflib |
michael@0 | 41 | import os |
michael@0 | 42 | import re |
michael@0 | 43 | import subprocess |
michael@0 | 44 | import sys |
michael@0 | 45 | import traceback |
michael@0 | 46 | |
michael@0 | 47 | # We don't bother checking files in these directories, because they're (a) auxiliary or (b) |
michael@0 | 48 | # imported code that doesn't follow our coding style. |
michael@0 | 49 | ignored_js_src_dirs = [ |
michael@0 | 50 | 'js/src/config/', # auxiliary stuff |
michael@0 | 51 | 'js/src/ctypes/libffi/', # imported code |
michael@0 | 52 | 'js/src/devtools/', # auxiliary stuff |
michael@0 | 53 | 'js/src/editline/', # imported code |
michael@0 | 54 | 'js/src/gdb/', # auxiliary stuff |
michael@0 | 55 | 'js/src/vtune/' # imported code |
michael@0 | 56 | ] |
michael@0 | 57 | |
michael@0 | 58 | # We ignore #includes of these files, because they don't follow the usual rules. |
michael@0 | 59 | included_inclnames_to_ignore = set([ |
michael@0 | 60 | 'ffi.h', # generated in ctypes/libffi/ |
michael@0 | 61 | 'devtools/sharkctl.h', # we ignore devtools/ in general |
michael@0 | 62 | 'devtools/Instruments.h', # we ignore devtools/ in general |
michael@0 | 63 | 'double-conversion.h', # strange MFBT case |
michael@0 | 64 | 'javascript-trace.h', # generated in $OBJDIR if HAVE_DTRACE is defined |
michael@0 | 65 | 'jsautokw.h', # generated in $OBJDIR |
michael@0 | 66 | 'jscustomallocator.h', # provided by embedders; allowed to be missing |
michael@0 | 67 | 'js-config.h', # generated in $OBJDIR |
michael@0 | 68 | 'pratom.h', # NSPR |
michael@0 | 69 | 'prcvar.h', # NSPR |
michael@0 | 70 | 'prinit.h', # NSPR |
michael@0 | 71 | 'prlink.h', # NSPR |
michael@0 | 72 | 'prlock.h', # NSPR |
michael@0 | 73 | 'prprf.h', # NSPR |
michael@0 | 74 | 'prthread.h', # NSPR |
michael@0 | 75 | 'prtypes.h', # NSPR |
michael@0 | 76 | 'selfhosted.out.h', # generated in $OBJDIR |
michael@0 | 77 | 'unicode/locid.h', # ICU |
michael@0 | 78 | 'unicode/numsys.h', # ICU |
michael@0 | 79 | 'unicode/ucal.h', # ICU |
michael@0 | 80 | 'unicode/uclean.h', # ICU |
michael@0 | 81 | 'unicode/ucol.h', # ICU |
michael@0 | 82 | 'unicode/udat.h', # ICU |
michael@0 | 83 | 'unicode/udatpg.h', # ICU |
michael@0 | 84 | 'unicode/uenum.h', # ICU |
michael@0 | 85 | 'unicode/unorm.h', # ICU |
michael@0 | 86 | 'unicode/unum.h', # ICU |
michael@0 | 87 | 'unicode/ustring.h', # ICU |
michael@0 | 88 | 'unicode/utypes.h', # ICU |
michael@0 | 89 | 'vtune/VTuneWrapper.h' # VTune |
michael@0 | 90 | ]) |
michael@0 | 91 | |
michael@0 | 92 | # These files have additional constraints on where they are #included, so we |
michael@0 | 93 | # ignore #includes of them when checking #include ordering. |
michael@0 | 94 | oddly_ordered_inclnames = set([ |
michael@0 | 95 | 'ctypes/typedefs.h', # Included multiple times in the body of ctypes/CTypes.h |
michael@0 | 96 | 'jsautokw.h', # Included in the body of frontend/TokenStream.h |
michael@0 | 97 | 'jswin.h', # Must be #included before <psapi.h> |
michael@0 | 98 | 'machine/endian.h', # Must be included after <sys/types.h> on BSD |
michael@0 | 99 | 'winbase.h', # Must precede other system headers(?) |
michael@0 | 100 | 'windef.h' # Must precede other system headers(?) |
michael@0 | 101 | ]) |
michael@0 | 102 | |
michael@0 | 103 | # The files in tests/style/ contain code that fails this checking in various |
michael@0 | 104 | # ways. Here is the output we expect. If the actual output differs from |
michael@0 | 105 | # this, one of the following must have happened. |
michael@0 | 106 | # - New SpiderMonkey code violates one of the checked rules. |
michael@0 | 107 | # - The tests/style/ files have changed without expected_output being changed |
michael@0 | 108 | # accordingly. |
michael@0 | 109 | # - This script has been broken somehow. |
michael@0 | 110 | # |
michael@0 | 111 | expected_output = '''\ |
michael@0 | 112 | js/src/tests/style/BadIncludes2.h:1: error: |
michael@0 | 113 | vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h" |
michael@0 | 114 | |
michael@0 | 115 | js/src/tests/style/BadIncludes.h:3: error: |
michael@0 | 116 | the file includes itself |
michael@0 | 117 | |
michael@0 | 118 | js/src/tests/style/BadIncludes.h:6: error: |
michael@0 | 119 | "BadIncludes2.h" is included using the wrong path; |
michael@0 | 120 | did you forget a prefix, or is the file not yet committed? |
michael@0 | 121 | |
michael@0 | 122 | js/src/tests/style/BadIncludes.h:8: error: |
michael@0 | 123 | <tests/style/BadIncludes2.h> should be included using |
michael@0 | 124 | the #include "..." form |
michael@0 | 125 | |
michael@0 | 126 | js/src/tests/style/BadIncludes.h:10: error: |
michael@0 | 127 | "stdio.h" is included using the wrong path; |
michael@0 | 128 | did you forget a prefix, or is the file not yet committed? |
michael@0 | 129 | |
michael@0 | 130 | js/src/tests/style/BadIncludesOrder-inl.h:5:6: error: |
michael@0 | 131 | "vm/Interpreter-inl.h" should be included after "jsscriptinlines.h" |
michael@0 | 132 | |
michael@0 | 133 | js/src/tests/style/BadIncludesOrder-inl.h:6:7: error: |
michael@0 | 134 | "jsscriptinlines.h" should be included after "js/Value.h" |
michael@0 | 135 | |
michael@0 | 136 | js/src/tests/style/BadIncludesOrder-inl.h:7:8: error: |
michael@0 | 137 | "js/Value.h" should be included after "ds/LifoAlloc.h" |
michael@0 | 138 | |
michael@0 | 139 | js/src/tests/style/BadIncludesOrder-inl.h:8:9: error: |
michael@0 | 140 | "ds/LifoAlloc.h" should be included after "jsapi.h" |
michael@0 | 141 | |
michael@0 | 142 | js/src/tests/style/BadIncludesOrder-inl.h:9:10: error: |
michael@0 | 143 | "jsapi.h" should be included after <stdio.h> |
michael@0 | 144 | |
michael@0 | 145 | js/src/tests/style/BadIncludesOrder-inl.h:10:11: error: |
michael@0 | 146 | <stdio.h> should be included after "mozilla/HashFunctions.h" |
michael@0 | 147 | |
michael@0 | 148 | js/src/tests/style/BadIncludesOrder-inl.h:27:28: error: |
michael@0 | 149 | "jsobj.h" should be included after "jsfun.h" |
michael@0 | 150 | |
michael@0 | 151 | (multiple files): error: |
michael@0 | 152 | header files form one or more cycles |
michael@0 | 153 | |
michael@0 | 154 | tests/style/HeaderCycleA1.h |
michael@0 | 155 | -> tests/style/HeaderCycleA2.h |
michael@0 | 156 | -> tests/style/HeaderCycleA3.h |
michael@0 | 157 | -> tests/style/HeaderCycleA1.h |
michael@0 | 158 | |
michael@0 | 159 | tests/style/HeaderCycleB1-inl.h |
michael@0 | 160 | -> tests/style/HeaderCycleB2-inl.h |
michael@0 | 161 | -> tests/style/HeaderCycleB3-inl.h |
michael@0 | 162 | -> tests/style/HeaderCycleB4-inl.h |
michael@0 | 163 | -> tests/style/HeaderCycleB1-inl.h |
michael@0 | 164 | -> tests/style/jsheadercycleB5inlines.h |
michael@0 | 165 | -> tests/style/HeaderCycleB1-inl.h |
michael@0 | 166 | -> tests/style/HeaderCycleB4-inl.h |
michael@0 | 167 | |
michael@0 | 168 | '''.splitlines(True) |
michael@0 | 169 | |
michael@0 | 170 | actual_output = [] |
michael@0 | 171 | |
michael@0 | 172 | |
michael@0 | 173 | def out(*lines): |
michael@0 | 174 | for line in lines: |
michael@0 | 175 | actual_output.append(line + '\n') |
michael@0 | 176 | |
michael@0 | 177 | |
michael@0 | 178 | def error(filename, linenum, *lines): |
michael@0 | 179 | location = filename |
michael@0 | 180 | if linenum is not None: |
michael@0 | 181 | location += ':' + str(linenum) |
michael@0 | 182 | out(location + ': error:') |
michael@0 | 183 | for line in (lines): |
michael@0 | 184 | out(' ' + line) |
michael@0 | 185 | out('') |
michael@0 | 186 | |
michael@0 | 187 | |
michael@0 | 188 | class FileKind(object): |
michael@0 | 189 | C = 1 |
michael@0 | 190 | CPP = 2 |
michael@0 | 191 | INL_H = 3 |
michael@0 | 192 | H = 4 |
michael@0 | 193 | TBL = 5 |
michael@0 | 194 | MSG = 6 |
michael@0 | 195 | |
michael@0 | 196 | @staticmethod |
michael@0 | 197 | def get(filename): |
michael@0 | 198 | if filename.endswith('.c'): |
michael@0 | 199 | return FileKind.C |
michael@0 | 200 | |
michael@0 | 201 | if filename.endswith('.cpp'): |
michael@0 | 202 | return FileKind.CPP |
michael@0 | 203 | |
michael@0 | 204 | if filename.endswith(('inlines.h', '-inl.h')): |
michael@0 | 205 | return FileKind.INL_H |
michael@0 | 206 | |
michael@0 | 207 | if filename.endswith('.h'): |
michael@0 | 208 | return FileKind.H |
michael@0 | 209 | |
michael@0 | 210 | if filename.endswith('.tbl'): |
michael@0 | 211 | return FileKind.TBL |
michael@0 | 212 | |
michael@0 | 213 | if filename.endswith('.msg'): |
michael@0 | 214 | return FileKind.MSG |
michael@0 | 215 | |
michael@0 | 216 | error(filename, None, 'unknown file kind') |
michael@0 | 217 | |
michael@0 | 218 | |
michael@0 | 219 | def get_all_filenames(): |
michael@0 | 220 | '''Get a list of all the files in the (Mercurial or Git) repository.''' |
michael@0 | 221 | cmds = [['hg', 'manifest', '-q'], ['git', 'ls-files', '--full-name', '../..']] |
michael@0 | 222 | for cmd in cmds: |
michael@0 | 223 | try: |
michael@0 | 224 | all_filenames = subprocess.check_output(cmd, universal_newlines=True, |
michael@0 | 225 | stderr=subprocess.PIPE).split('\n') |
michael@0 | 226 | return all_filenames |
michael@0 | 227 | except: |
michael@0 | 228 | continue |
michael@0 | 229 | else: |
michael@0 | 230 | raise Exception('failed to run any of the repo manifest commands', cmds) |
michael@0 | 231 | |
michael@0 | 232 | |
michael@0 | 233 | def check_style(): |
michael@0 | 234 | # We deal with two kinds of name. |
michael@0 | 235 | # - A "filename" is a full path to a file from the repository root. |
michael@0 | 236 | # - An "inclname" is how a file is referred to in a #include statement. |
michael@0 | 237 | # |
michael@0 | 238 | # Examples (filename -> inclname) |
michael@0 | 239 | # - "mfbt/Attributes.h" -> "mozilla/Attributes.h" |
michael@0 | 240 | # - "js/public/Vector.h" -> "js/Vector.h" |
michael@0 | 241 | # - "js/src/vm/String.h" -> "vm/String.h" |
michael@0 | 242 | |
michael@0 | 243 | mfbt_inclnames = set() # type: set(inclname) |
michael@0 | 244 | js_names = dict() # type: dict(filename, inclname) |
michael@0 | 245 | |
michael@0 | 246 | # Select the appropriate files. |
michael@0 | 247 | for filename in get_all_filenames(): |
michael@0 | 248 | if filename.startswith('mfbt/') and filename.endswith('.h'): |
michael@0 | 249 | inclname = 'mozilla/' + filename[len('mfbt/'):] |
michael@0 | 250 | mfbt_inclnames.add(inclname) |
michael@0 | 251 | |
michael@0 | 252 | if filename.startswith('js/public/') and filename.endswith('.h'): |
michael@0 | 253 | inclname = 'js/' + filename[len('js/public/'):] |
michael@0 | 254 | js_names[filename] = inclname |
michael@0 | 255 | |
michael@0 | 256 | if filename.startswith('js/src/') and \ |
michael@0 | 257 | not filename.startswith(tuple(ignored_js_src_dirs)) and \ |
michael@0 | 258 | filename.endswith(('.c', '.cpp', '.h', '.tbl', '.msg')): |
michael@0 | 259 | inclname = filename[len('js/src/'):] |
michael@0 | 260 | js_names[filename] = inclname |
michael@0 | 261 | |
michael@0 | 262 | all_inclnames = mfbt_inclnames | set(js_names.values()) |
michael@0 | 263 | |
michael@0 | 264 | edges = dict() # type: dict(inclname, set(inclname)) |
michael@0 | 265 | |
michael@0 | 266 | # We don't care what's inside the MFBT files, but because they are |
michael@0 | 267 | # #included from JS files we have to add them to the inclusion graph. |
michael@0 | 268 | for inclname in mfbt_inclnames: |
michael@0 | 269 | edges[inclname] = set() |
michael@0 | 270 | |
michael@0 | 271 | # Process all the JS files. |
michael@0 | 272 | for filename in js_names.keys(): |
michael@0 | 273 | inclname = js_names[filename] |
michael@0 | 274 | file_kind = FileKind.get(filename) |
michael@0 | 275 | if file_kind == FileKind.C or file_kind == FileKind.CPP or \ |
michael@0 | 276 | file_kind == FileKind.H or file_kind == FileKind.INL_H: |
michael@0 | 277 | included_h_inclnames = set() # type: set(inclname) |
michael@0 | 278 | |
michael@0 | 279 | # This script is run in js/src/, so prepend '../../' to get to the root of the Mozilla |
michael@0 | 280 | # source tree. |
michael@0 | 281 | with open(os.path.join('../..', filename)) as f: |
michael@0 | 282 | do_file(filename, inclname, file_kind, f, all_inclnames, included_h_inclnames) |
michael@0 | 283 | |
michael@0 | 284 | edges[inclname] = included_h_inclnames |
michael@0 | 285 | |
michael@0 | 286 | find_cycles(all_inclnames, edges) |
michael@0 | 287 | |
michael@0 | 288 | # Compare expected and actual output. |
michael@0 | 289 | difflines = difflib.unified_diff(expected_output, actual_output, |
michael@0 | 290 | fromfile='check_spider_monkey_style.py expected output', |
michael@0 | 291 | tofile='check_spider_monkey_style.py actual output') |
michael@0 | 292 | ok = True |
michael@0 | 293 | for diffline in difflines: |
michael@0 | 294 | ok = False |
michael@0 | 295 | print(diffline, end='') |
michael@0 | 296 | |
michael@0 | 297 | return ok |
michael@0 | 298 | |
michael@0 | 299 | |
michael@0 | 300 | def module_name(name): |
michael@0 | 301 | '''Strip the trailing .cpp, .h, inlines.h or -inl.h from a filename.''' |
michael@0 | 302 | |
michael@0 | 303 | return name.replace('inlines.h', '').replace('-inl.h', '').replace('.h', '').replace('.cpp', '') |
michael@0 | 304 | |
michael@0 | 305 | |
michael@0 | 306 | def is_module_header(enclosing_inclname, header_inclname): |
michael@0 | 307 | '''Determine if an included name is the "module header", i.e. should be |
michael@0 | 308 | first in the file.''' |
michael@0 | 309 | |
michael@0 | 310 | module = module_name(enclosing_inclname) |
michael@0 | 311 | |
michael@0 | 312 | # Normal case, e.g. module == "foo/Bar", header_inclname == "foo/Bar.h". |
michael@0 | 313 | if module == module_name(header_inclname): |
michael@0 | 314 | return True |
michael@0 | 315 | |
michael@0 | 316 | # A public header, e.g. module == "foo/Bar", header_inclname == "js/Bar.h". |
michael@0 | 317 | m = re.match(r'js\/(.*)\.h', header_inclname) |
michael@0 | 318 | if m is not None and module.endswith('/' + m.group(1)): |
michael@0 | 319 | return True |
michael@0 | 320 | |
michael@0 | 321 | return False |
michael@0 | 322 | |
michael@0 | 323 | |
michael@0 | 324 | class Include(object): |
michael@0 | 325 | '''Important information for a single #include statement.''' |
michael@0 | 326 | |
michael@0 | 327 | def __init__(self, inclname, linenum, is_system): |
michael@0 | 328 | self.inclname = inclname |
michael@0 | 329 | self.linenum = linenum |
michael@0 | 330 | self.is_system = is_system |
michael@0 | 331 | |
michael@0 | 332 | def isLeaf(self): |
michael@0 | 333 | return True |
michael@0 | 334 | |
michael@0 | 335 | def section(self, enclosing_inclname): |
michael@0 | 336 | '''Identify which section inclname belongs to. |
michael@0 | 337 | |
michael@0 | 338 | The section numbers are as follows. |
michael@0 | 339 | 0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp) |
michael@0 | 340 | 1. mozilla/Foo.h |
michael@0 | 341 | 2. <foo.h> or <foo> |
michael@0 | 342 | 3. jsfoo.h, prmjtime.h, etc |
michael@0 | 343 | 4. foo/Bar.h |
michael@0 | 344 | 5. jsfooinlines.h |
michael@0 | 345 | 6. foo/Bar-inl.h |
michael@0 | 346 | 7. non-.h, e.g. *.tbl, *.msg |
michael@0 | 347 | ''' |
michael@0 | 348 | |
michael@0 | 349 | if self.is_system: |
michael@0 | 350 | return 2 |
michael@0 | 351 | |
michael@0 | 352 | if not self.inclname.endswith('.h'): |
michael@0 | 353 | return 7 |
michael@0 | 354 | |
michael@0 | 355 | # A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need |
michael@0 | 356 | # special handling. |
michael@0 | 357 | if is_module_header(enclosing_inclname, self.inclname): |
michael@0 | 358 | return 0 |
michael@0 | 359 | |
michael@0 | 360 | if '/' in self.inclname: |
michael@0 | 361 | if self.inclname.startswith('mozilla/'): |
michael@0 | 362 | return 1 |
michael@0 | 363 | |
michael@0 | 364 | if self.inclname.endswith('-inl.h'): |
michael@0 | 365 | return 6 |
michael@0 | 366 | |
michael@0 | 367 | return 4 |
michael@0 | 368 | |
michael@0 | 369 | if self.inclname.endswith('inlines.h'): |
michael@0 | 370 | return 5 |
michael@0 | 371 | |
michael@0 | 372 | return 3 |
michael@0 | 373 | |
michael@0 | 374 | def quote(self): |
michael@0 | 375 | if self.is_system: |
michael@0 | 376 | return '<' + self.inclname + '>' |
michael@0 | 377 | else: |
michael@0 | 378 | return '"' + self.inclname + '"' |
michael@0 | 379 | |
michael@0 | 380 | |
michael@0 | 381 | class HashIfBlock(object): |
michael@0 | 382 | '''Important information about a #if/#endif block. |
michael@0 | 383 | |
michael@0 | 384 | A #if/#endif block is the contents of a #if/#endif (or similar) section. |
michael@0 | 385 | The top-level block, which is not within a #if/#endif pair, is also |
michael@0 | 386 | considered a block. |
michael@0 | 387 | |
michael@0 | 388 | Each leaf is either an Include (representing a #include), or another |
michael@0 | 389 | nested HashIfBlock.''' |
michael@0 | 390 | def __init__(self): |
michael@0 | 391 | self.kids = [] |
michael@0 | 392 | |
michael@0 | 393 | def isLeaf(self): |
michael@0 | 394 | return False |
michael@0 | 395 | |
michael@0 | 396 | |
michael@0 | 397 | def do_file(filename, inclname, file_kind, f, all_inclnames, included_h_inclnames): |
michael@0 | 398 | block_stack = [HashIfBlock()] |
michael@0 | 399 | |
michael@0 | 400 | # Extract the #include statements as a tree of IBlocks and IIncludes. |
michael@0 | 401 | for linenum, line in enumerate(f, start=1): |
michael@0 | 402 | # Look for a |#include "..."| line. |
michael@0 | 403 | m = re.match(r'\s*#\s*include\s+"([^"]*)"', line) |
michael@0 | 404 | if m is not None: |
michael@0 | 405 | block_stack[-1].kids.append(Include(m.group(1), linenum, False)) |
michael@0 | 406 | |
michael@0 | 407 | # Look for a |#include <...>| line. |
michael@0 | 408 | m = re.match(r'\s*#\s*include\s+<([^>]*)>', line) |
michael@0 | 409 | if m is not None: |
michael@0 | 410 | block_stack[-1].kids.append(Include(m.group(1), linenum, True)) |
michael@0 | 411 | |
michael@0 | 412 | # Look for a |#{if,ifdef,ifndef}| line. |
michael@0 | 413 | m = re.match(r'\s*#\s*(if|ifdef|ifndef)\b', line) |
michael@0 | 414 | if m is not None: |
michael@0 | 415 | # Open a new block. |
michael@0 | 416 | new_block = HashIfBlock() |
michael@0 | 417 | block_stack[-1].kids.append(new_block) |
michael@0 | 418 | block_stack.append(new_block) |
michael@0 | 419 | |
michael@0 | 420 | # Look for a |#{elif,else}| line. |
michael@0 | 421 | m = re.match(r'\s*#\s*(elif|else)\b', line) |
michael@0 | 422 | if m is not None: |
michael@0 | 423 | # Close the current block, and open an adjacent one. |
michael@0 | 424 | block_stack.pop() |
michael@0 | 425 | new_block = HashIfBlock() |
michael@0 | 426 | block_stack[-1].kids.append(new_block) |
michael@0 | 427 | block_stack.append(new_block) |
michael@0 | 428 | |
michael@0 | 429 | # Look for a |#endif| line. |
michael@0 | 430 | m = re.match(r'\s*#\s*endif\b', line) |
michael@0 | 431 | if m is not None: |
michael@0 | 432 | # Close the current block. |
michael@0 | 433 | block_stack.pop() |
michael@0 | 434 | |
michael@0 | 435 | def check_include_statement(include): |
michael@0 | 436 | '''Check the style of a single #include statement.''' |
michael@0 | 437 | |
michael@0 | 438 | if include.is_system: |
michael@0 | 439 | # Check it is not a known local file (in which case it's probably a system header). |
michael@0 | 440 | if include.inclname in included_inclnames_to_ignore or \ |
michael@0 | 441 | include.inclname in all_inclnames: |
michael@0 | 442 | error(filename, include.linenum, |
michael@0 | 443 | include.quote() + ' should be included using', |
michael@0 | 444 | 'the #include "..." form') |
michael@0 | 445 | |
michael@0 | 446 | else: |
michael@0 | 447 | if include.inclname not in included_inclnames_to_ignore: |
michael@0 | 448 | included_kind = FileKind.get(include.inclname) |
michael@0 | 449 | |
michael@0 | 450 | # Check the #include path has the correct form. |
michael@0 | 451 | if include.inclname not in all_inclnames: |
michael@0 | 452 | error(filename, include.linenum, |
michael@0 | 453 | include.quote() + ' is included ' + 'using the wrong path;', |
michael@0 | 454 | 'did you forget a prefix, or is the file not yet committed?') |
michael@0 | 455 | |
michael@0 | 456 | # Record inclusions of .h files for cycle detection later. |
michael@0 | 457 | # (Exclude .tbl and .msg files.) |
michael@0 | 458 | elif included_kind == FileKind.H or included_kind == FileKind.INL_H: |
michael@0 | 459 | included_h_inclnames.add(include.inclname) |
michael@0 | 460 | |
michael@0 | 461 | # Check a H file doesn't #include an INL_H file. |
michael@0 | 462 | if file_kind == FileKind.H and included_kind == FileKind.INL_H: |
michael@0 | 463 | error(filename, include.linenum, |
michael@0 | 464 | 'vanilla header includes an inline-header file ' + include.quote()) |
michael@0 | 465 | |
michael@0 | 466 | # Check a file doesn't #include itself. (We do this here because the cycle |
michael@0 | 467 | # detection below doesn't detect this case.) |
michael@0 | 468 | if inclname == include.inclname: |
michael@0 | 469 | error(filename, include.linenum, 'the file includes itself') |
michael@0 | 470 | |
michael@0 | 471 | def check_includes_order(include1, include2): |
michael@0 | 472 | '''Check the ordering of two #include statements.''' |
michael@0 | 473 | |
michael@0 | 474 | if include1.inclname in oddly_ordered_inclnames or \ |
michael@0 | 475 | include2.inclname in oddly_ordered_inclnames: |
michael@0 | 476 | return |
michael@0 | 477 | |
michael@0 | 478 | section1 = include1.section(inclname) |
michael@0 | 479 | section2 = include2.section(inclname) |
michael@0 | 480 | if (section1 > section2) or \ |
michael@0 | 481 | ((section1 == section2) and (include1.inclname.lower() > include2.inclname.lower())): |
michael@0 | 482 | error(filename, str(include1.linenum) + ':' + str(include2.linenum), |
michael@0 | 483 | include1.quote() + ' should be included after ' + include2.quote()) |
michael@0 | 484 | |
michael@0 | 485 | # The #include statements in the files in assembler/ and yarr/ have all manner of implicit |
michael@0 | 486 | # ordering requirements. Boo. Ignore them. |
michael@0 | 487 | skip_order_checking = inclname.startswith(('assembler/', 'yarr/')) |
michael@0 | 488 | |
michael@0 | 489 | # Check the extracted #include statements, both individually, and the ordering of |
michael@0 | 490 | # adjacent pairs that live in the same block. |
michael@0 | 491 | def pair_traverse(prev, this): |
michael@0 | 492 | if this.isLeaf(): |
michael@0 | 493 | check_include_statement(this) |
michael@0 | 494 | if prev is not None and prev.isLeaf() and not skip_order_checking: |
michael@0 | 495 | check_includes_order(prev, this) |
michael@0 | 496 | else: |
michael@0 | 497 | for prev2, this2 in zip([None] + this.kids[0:-1], this.kids): |
michael@0 | 498 | pair_traverse(prev2, this2) |
michael@0 | 499 | |
michael@0 | 500 | pair_traverse(None, block_stack[-1]) |
michael@0 | 501 | |
michael@0 | 502 | |
michael@0 | 503 | def find_cycles(all_inclnames, edges): |
michael@0 | 504 | '''Find and draw any cycles.''' |
michael@0 | 505 | |
michael@0 | 506 | SCCs = tarjan(all_inclnames, edges) |
michael@0 | 507 | |
michael@0 | 508 | # The various sorted() calls below ensure the output is deterministic. |
michael@0 | 509 | |
michael@0 | 510 | def draw_SCC(c): |
michael@0 | 511 | cset = set(c) |
michael@0 | 512 | drawn = set() |
michael@0 | 513 | def draw(v, indent): |
michael@0 | 514 | out(' ' * indent + ('-> ' if indent else ' ') + v) |
michael@0 | 515 | if v in drawn: |
michael@0 | 516 | return |
michael@0 | 517 | drawn.add(v) |
michael@0 | 518 | for succ in sorted(edges[v]): |
michael@0 | 519 | if succ in cset: |
michael@0 | 520 | draw(succ, indent + 1) |
michael@0 | 521 | draw(sorted(c)[0], 0) |
michael@0 | 522 | out('') |
michael@0 | 523 | |
michael@0 | 524 | have_drawn_an_SCC = False |
michael@0 | 525 | for scc in sorted(SCCs): |
michael@0 | 526 | if len(scc) != 1: |
michael@0 | 527 | if not have_drawn_an_SCC: |
michael@0 | 528 | error('(multiple files)', None, 'header files form one or more cycles') |
michael@0 | 529 | have_drawn_an_SCC = True |
michael@0 | 530 | |
michael@0 | 531 | draw_SCC(scc) |
michael@0 | 532 | |
michael@0 | 533 | |
michael@0 | 534 | # Tarjan's algorithm for finding the strongly connected components (SCCs) of a graph. |
michael@0 | 535 | # https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm |
michael@0 | 536 | def tarjan(V, E): |
michael@0 | 537 | vertex_index = {} |
michael@0 | 538 | vertex_lowlink = {} |
michael@0 | 539 | index = 0 |
michael@0 | 540 | S = [] |
michael@0 | 541 | all_SCCs = [] |
michael@0 | 542 | |
michael@0 | 543 | def strongconnect(v, index): |
michael@0 | 544 | # Set the depth index for v to the smallest unused index |
michael@0 | 545 | vertex_index[v] = index |
michael@0 | 546 | vertex_lowlink[v] = index |
michael@0 | 547 | index += 1 |
michael@0 | 548 | S.append(v) |
michael@0 | 549 | |
michael@0 | 550 | # Consider successors of v |
michael@0 | 551 | for w in E[v]: |
michael@0 | 552 | if w not in vertex_index: |
michael@0 | 553 | # Successor w has not yet been visited; recurse on it |
michael@0 | 554 | index = strongconnect(w, index) |
michael@0 | 555 | vertex_lowlink[v] = min(vertex_lowlink[v], vertex_lowlink[w]) |
michael@0 | 556 | elif w in S: |
michael@0 | 557 | # Successor w is in stack S and hence in the current SCC |
michael@0 | 558 | vertex_lowlink[v] = min(vertex_lowlink[v], vertex_index[w]) |
michael@0 | 559 | |
michael@0 | 560 | # If v is a root node, pop the stack and generate an SCC |
michael@0 | 561 | if vertex_lowlink[v] == vertex_index[v]: |
michael@0 | 562 | i = S.index(v) |
michael@0 | 563 | scc = S[i:] |
michael@0 | 564 | del S[i:] |
michael@0 | 565 | all_SCCs.append(scc) |
michael@0 | 566 | |
michael@0 | 567 | return index |
michael@0 | 568 | |
michael@0 | 569 | for v in V: |
michael@0 | 570 | if v not in vertex_index: |
michael@0 | 571 | index = strongconnect(v, index) |
michael@0 | 572 | |
michael@0 | 573 | return all_SCCs |
michael@0 | 574 | |
michael@0 | 575 | |
michael@0 | 576 | def main(): |
michael@0 | 577 | ok = check_style() |
michael@0 | 578 | |
michael@0 | 579 | if ok: |
michael@0 | 580 | print('TEST-PASS | check_spidermonkey_style.py | ok') |
michael@0 | 581 | else: |
michael@0 | 582 | print('TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | actual output does not match expected output; diff is above') |
michael@0 | 583 | |
michael@0 | 584 | sys.exit(0 if ok else 1) |
michael@0 | 585 | |
michael@0 | 586 | |
michael@0 | 587 | if __name__ == '__main__': |
michael@0 | 588 | main() |