config/check_spidermonkey_style.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=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()

mercurial