Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / config / check_spidermonkey_style.py
blob5e4200001cc53917c4bbc326fcda243b163abc4c
1 # vim: set ts=8 sts=4 et sw=4 tw=99:
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 # This script checks various aspects of SpiderMonkey code style. The current checks are as
8 # follows.
10 # We check the following things in headers.
12 # - No cyclic dependencies.
14 # - No normal header should #include a inlines.h/-inl.h file.
16 # - #ifndef wrappers should have the right form. (XXX: not yet implemented)
17 # - Every header file should have one.
18 # - The guard name used should be appropriate for the filename.
20 # We check the following things in all files.
22 # - #includes should have full paths, e.g. "jit/Ion.h", not "Ion.h".
24 # - #includes should use the appropriate form for system headers (<...>) and
25 # local headers ("...").
27 # - #includes should be ordered correctly.
28 # - Each one should be in the correct section.
29 # - Alphabetical order should be used within sections.
30 # - Sections should be in the right order.
31 # Note that the presence of #if/#endif blocks complicates things, to the
32 # point that it's not always clear where a conditionally-compiled #include
33 # statement should go, even to a human. Therefore, we check the #include
34 # statements within each #if/#endif block (including nested ones) in
35 # isolation, but don't try to do any order checking between such blocks.
36 # ----------------------------------------------------------------------------
38 import difflib
39 import os
40 import re
41 import sys
43 # We don't bother checking files in these directories, because they're (a) auxiliary or (b)
44 # imported code that doesn't follow our coding style.
45 ignored_js_src_dirs = [
46 "js/src/config/", # auxiliary stuff
47 "js/src/ctypes/libffi/", # imported code
48 "js/src/devtools/", # auxiliary stuff
49 "js/src/editline/", # imported code
50 "js/src/gdb/", # auxiliary stuff
51 "js/src/vtune/", # imported code
52 "js/src/zydis/", # imported code
55 # We ignore #includes of these files, because they don't follow the usual rules.
56 included_inclnames_to_ignore = set(
58 "ffi.h", # generated in ctypes/libffi/
59 "devtools/Instruments.h", # we ignore devtools/ in general
60 "double-conversion/double-conversion.h", # strange MFBT case
61 "javascript-trace.h", # generated in $OBJDIR if HAVE_DTRACE is defined
62 "frontend/ReservedWordsGenerated.h", # generated in $OBJDIR
63 "frontend/smoosh_generated.h", # generated in $OBJDIR
64 "gc/StatsPhasesGenerated.h", # generated in $OBJDIR
65 "gc/StatsPhasesGenerated.inc", # generated in $OBJDIR
66 "jit/ABIFunctionTypeGenerated.h", # generated in $OBJDIR"
67 "jit/AtomicOperationsGenerated.h", # generated in $OBJDIR
68 "jit/CacheIROpsGenerated.h", # generated in $OBJDIR
69 "jit/LIROpsGenerated.h", # generated in $OBJDIR
70 "jit/MIROpsGenerated.h", # generated in $OBJDIR
71 "js/ProfilingCategoryList.h", # comes from mozglue/baseprofiler
72 "jscustomallocator.h", # provided by embedders; allowed to be missing
73 "js-config.h", # generated in $OBJDIR
74 "fdlibm.h", # fdlibm
75 "FuzzerDefs.h", # included without a path
76 "FuzzingInterface.h", # included without a path
77 "ICU4XGraphemeClusterSegmenter.h", # ICU4X
78 "ICU4XSentenceSegmenter.h", # ICU4X
79 "ICU4XWordSegmenter.h", # ICU4X
80 "mozmemory.h", # included without a path
81 "pratom.h", # NSPR
82 "prcvar.h", # NSPR
83 "prerror.h", # NSPR
84 "prinit.h", # NSPR
85 "prio.h", # NSPR
86 "private/pprio.h", # NSPR
87 "prlink.h", # NSPR
88 "prlock.h", # NSPR
89 "prprf.h", # NSPR
90 "prthread.h", # NSPR
91 "prtypes.h", # NSPR
92 "selfhosted.out.h", # generated in $OBJDIR
93 "shellmoduleloader.out.h", # generated in $OBJDIR
94 "unicode/locid.h", # ICU
95 "unicode/uchar.h", # ICU
96 "unicode/uniset.h", # ICU
97 "unicode/unistr.h", # ICU
98 "unicode/utypes.h", # ICU
99 "vtune/VTuneWrapper.h", # VTune
100 "wasm/WasmBuiltinModuleGenerated.h", # generated in $OBJDIR"
101 "zydis/ZydisAPI.h", # Zydis
105 deprecated_inclnames = {
106 "mozilla/Unused.h": "Use [[nodiscard]] and (void)expr casts instead.",
109 # JSAPI functions should be included through headers from js/public instead of
110 # using the old, catch-all jsapi.h file.
111 deprecated_inclnames_in_header = {
112 "jsapi.h": "Prefer including headers from js/public.",
115 # Temporary exclusions for files which still need to include jsapi.h.
116 deprecated_inclnames_in_header_excludes = {
117 "js/src/vm/Compartment-inl.h", # for JS::InformalValueTypeName
118 "js/src/jsapi-tests/tests.h", # for JS_ValueToSource
121 # These files have additional constraints on where they are #included, so we
122 # ignore #includes of them when checking #include ordering.
123 oddly_ordered_inclnames = set(
125 "ctypes/typedefs.h", # Included multiple times in the body of ctypes/CTypes.h
126 # Included in the body of frontend/TokenStream.h
127 "frontend/ReservedWordsGenerated.h",
128 "gc/StatsPhasesGenerated.h", # Included in the body of gc/Statistics.h
129 "gc/StatsPhasesGenerated.inc", # Included in the body of gc/Statistics.cpp
130 "psapi.h", # Must be included after "util/WindowsWrapper.h" on Windows
131 "machine/endian.h", # Must be included after <sys/types.h> on BSD
132 "process.h", # Windows-specific
133 "winbase.h", # Must precede other system headers(?)
134 "windef.h", # Must precede other system headers(?)
135 "windows.h", # Must precede other system headers(?)
139 # The files in tests/style/ contain code that fails this checking in various
140 # ways. Here is the output we expect. If the actual output differs from
141 # this, one of the following must have happened.
142 # - New SpiderMonkey code violates one of the checked rules.
143 # - The tests/style/ files have changed without expected_output being changed
144 # accordingly.
145 # - This script has been broken somehow.
147 expected_output = """\
148 js/src/tests/style/BadIncludes.h:3: error:
149 the file includes itself
151 js/src/tests/style/BadIncludes.h:6: error:
152 "BadIncludes2.h" is included using the wrong path;
153 did you forget a prefix, or is the file not yet committed?
155 js/src/tests/style/BadIncludes.h:8: error:
156 <tests/style/BadIncludes2.h> should be included using
157 the #include "..." form
159 js/src/tests/style/BadIncludes.h:10: error:
160 "stdio.h" is included using the wrong path;
161 did you forget a prefix, or is the file not yet committed?
163 js/src/tests/style/BadIncludes.h:12: error:
164 "mozilla/Unused.h" is deprecated: Use [[nodiscard]] and (void)expr casts instead.
166 js/src/tests/style/BadIncludes2.h:1: error:
167 vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h"
169 js/src/tests/style/BadIncludesOrder-inl.h:5:6: error:
170 "vm/JSScript-inl.h" should be included after "vm/Interpreter-inl.h"
172 js/src/tests/style/BadIncludesOrder-inl.h:6:7: error:
173 "vm/Interpreter-inl.h" should be included after "js/Value.h"
175 js/src/tests/style/BadIncludesOrder-inl.h:7:8: error:
176 "js/Value.h" should be included after "ds/LifoAlloc.h"
178 js/src/tests/style/BadIncludesOrder-inl.h:9: error:
179 "jsapi.h" is deprecated: Prefer including headers from js/public.
181 js/src/tests/style/BadIncludesOrder-inl.h:8:9: error:
182 "ds/LifoAlloc.h" should be included after "jsapi.h"
184 js/src/tests/style/BadIncludesOrder-inl.h:9:10: error:
185 "jsapi.h" should be included after <stdio.h>
187 js/src/tests/style/BadIncludesOrder-inl.h:10:11: error:
188 <stdio.h> should be included after "mozilla/HashFunctions.h"
190 js/src/tests/style/BadIncludesOrder-inl.h:20: error:
191 "jsapi.h" is deprecated: Prefer including headers from js/public.
193 js/src/tests/style/BadIncludesOrder-inl.h:28:29: error:
194 "vm/JSScript.h" should be included after "vm/JSFunction.h"
196 (multiple files): error:
197 header files form one or more cycles
199 tests/style/HeaderCycleA1.h
200 -> tests/style/HeaderCycleA2.h
201 -> tests/style/HeaderCycleA3.h
202 -> tests/style/HeaderCycleA1.h
204 tests/style/HeaderCycleB1-inl.h
205 -> tests/style/HeaderCycleB2-inl.h
206 -> tests/style/HeaderCycleB3-inl.h
207 -> tests/style/HeaderCycleB4-inl.h
208 -> tests/style/HeaderCycleB1-inl.h
209 -> tests/style/jsheadercycleB5inlines.h
210 -> tests/style/HeaderCycleB1-inl.h
211 -> tests/style/HeaderCycleB4-inl.h
213 """.splitlines(
214 True
217 actual_output = []
220 def out(*lines):
221 for line in lines:
222 actual_output.append(line + "\n")
225 def error(filename, linenum, *lines):
226 location = filename
227 if linenum is not None:
228 location += ":" + str(linenum)
229 out(location + ": error:")
230 for line in lines:
231 out(" " + line)
232 out("")
235 class FileKind(object):
236 C = 1
237 CPP = 2
238 INL_H = 3
239 H = 4
240 TBL = 5
241 MSG = 6
243 @staticmethod
244 def get(filename):
245 if filename.endswith(".c"):
246 return FileKind.C
248 if filename.endswith(".cpp"):
249 return FileKind.CPP
251 if filename.endswith(("inlines.h", "-inl.h")):
252 return FileKind.INL_H
254 if filename.endswith(".h"):
255 return FileKind.H
257 if filename.endswith(".tbl"):
258 return FileKind.TBL
260 if filename.endswith(".msg"):
261 return FileKind.MSG
263 error(filename, None, "unknown file kind")
266 def check_style(enable_fixup):
267 # We deal with two kinds of name.
268 # - A "filename" is a full path to a file from the repository root.
269 # - An "inclname" is how a file is referred to in a #include statement.
271 # Examples (filename -> inclname)
272 # - "mfbt/Attributes.h" -> "mozilla/Attributes.h"
273 # - "mozglue/misc/TimeStamp.h -> "mozilla/TimeStamp.h"
274 # - "memory/mozalloc/mozalloc.h -> "mozilla/mozalloc.h"
275 # - "js/public/Vector.h" -> "js/Vector.h"
276 # - "js/src/vm/String.h" -> "vm/String.h"
278 non_js_dirnames = (
279 "mfbt/",
280 "memory/mozalloc/",
281 "mozglue/",
282 "intl/components/",
283 ) # type: tuple(str)
284 non_js_inclnames = set() # type: set(inclname)
285 js_names = dict() # type: dict(filename, inclname)
287 # Process files in js/src.
288 js_src_root = os.path.join("js", "src")
289 for dirpath, dirnames, filenames in os.walk(js_src_root):
290 if dirpath == js_src_root:
291 # Skip any subdirectories that contain a config.status file
292 # (likely objdirs).
293 builddirs = []
294 for dirname in dirnames:
295 path = os.path.join(dirpath, dirname, "config.status")
296 if os.path.isfile(path):
297 builddirs.append(dirname)
298 for dirname in builddirs:
299 dirnames.remove(dirname)
300 for filename in filenames:
301 filepath = os.path.join(dirpath, filename).replace("\\", "/")
302 if not filepath.startswith(
303 tuple(ignored_js_src_dirs)
304 ) and filepath.endswith((".c", ".cpp", ".h", ".tbl", ".msg")):
305 inclname = filepath[len("js/src/") :]
306 js_names[filepath] = inclname
308 # Look for header files in directories in non_js_dirnames.
309 for non_js_dir in non_js_dirnames:
310 for dirpath, dirnames, filenames in os.walk(non_js_dir):
311 for filename in filenames:
312 if filename.endswith(".h"):
313 inclname = "mozilla/" + filename
314 if non_js_dir == "intl/components/":
315 inclname = "mozilla/intl/" + filename
316 non_js_inclnames.add(inclname)
318 # Look for header files in js/public.
319 js_public_root = os.path.join("js", "public")
320 for dirpath, dirnames, filenames in os.walk(js_public_root):
321 for filename in filenames:
322 if filename.endswith((".h", ".msg")):
323 filepath = os.path.join(dirpath, filename).replace("\\", "/")
324 inclname = "js/" + filepath[len("js/public/") :]
325 js_names[filepath] = inclname
327 all_inclnames = non_js_inclnames | set(js_names.values())
329 edges = dict() # type: dict(inclname, set(inclname))
331 # We don't care what's inside the MFBT and MOZALLOC files, but because they
332 # are #included from JS files we have to add them to the inclusion graph.
333 for inclname in non_js_inclnames:
334 edges[inclname] = set()
336 # Process all the JS files.
337 for filename in sorted(js_names.keys()):
338 inclname = js_names[filename]
339 file_kind = FileKind.get(filename)
340 if (
341 file_kind == FileKind.C
342 or file_kind == FileKind.CPP
343 or file_kind == FileKind.H
344 or file_kind == FileKind.INL_H
346 included_h_inclnames = set() # type: set(inclname)
348 with open(filename, encoding="utf-8") as f:
349 code = read_file(f)
351 if enable_fixup:
352 code = code.sorted(inclname)
353 with open(filename, "w") as f:
354 f.write(code.to_source())
356 check_file(
357 filename, inclname, file_kind, code, all_inclnames, included_h_inclnames
360 edges[inclname] = included_h_inclnames
362 find_cycles(all_inclnames, edges)
364 # Compare expected and actual output.
365 difflines = difflib.unified_diff(
366 expected_output,
367 actual_output,
368 fromfile="check_spidermonkey_style.py expected output",
369 tofile="check_spidermonkey_style.py actual output",
371 ok = True
372 for diffline in difflines:
373 ok = False
374 print(diffline, end="")
376 return ok
379 def module_name(name):
380 """Strip the trailing .cpp, .h, inlines.h or -inl.h from a filename."""
382 return (
383 name.replace("inlines.h", "")
384 .replace("-inl.h", "")
385 .replace(".h", "")
386 .replace(".cpp", "")
387 ) # NOQA: E501
390 def is_module_header(enclosing_inclname, header_inclname):
391 """Determine if an included name is the "module header", i.e. should be
392 first in the file."""
394 module = module_name(enclosing_inclname)
396 # Normal case, for example:
397 # module == "vm/Runtime", header_inclname == "vm/Runtime.h".
398 if module == module_name(header_inclname):
399 return True
401 # A public header, for example:
403 # module == "vm/CharacterEncoding",
404 # header_inclname == "js/CharacterEncoding.h"
406 # or (for implementation files for js/public/*/*.h headers)
408 # module == "vm/SourceHook",
409 # header_inclname == "js/experimental/SourceHook.h"
410 m = re.match(r"js\/.*?([^\/]+)\.h", header_inclname)
411 if m is not None and module.endswith("/" + m.group(1)):
412 return True
414 return False
417 class Include(object):
418 """Important information for a single #include statement."""
420 def __init__(self, include_prefix, inclname, line_suffix, linenum, is_system):
421 self.include_prefix = include_prefix
422 self.line_suffix = line_suffix
423 self.inclname = inclname
424 self.linenum = linenum
425 self.is_system = is_system
427 def is_style_relevant(self):
428 # Includes are style-relevant; that is, they're checked by the pairwise
429 # style-checking algorithm in check_file.
430 return True
432 def section(self, enclosing_inclname):
433 """Identify which section inclname belongs to.
435 The section numbers are as follows.
436 0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp)
437 1. mozilla/Foo.h
438 2. <foo.h> or <foo>
439 3. jsfoo.h, prmjtime.h, etc
440 4. foo/Bar.h
441 5. jsfooinlines.h
442 6. foo/Bar-inl.h
443 7. non-.h, e.g. *.tbl, *.msg (these can be scattered throughout files)
446 if self.is_system:
447 return 2
449 if not self.inclname.endswith(".h"):
450 return 7
452 # A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need
453 # special handling.
454 if is_module_header(enclosing_inclname, self.inclname):
455 return 0
457 if "/" in self.inclname:
458 if self.inclname.startswith("mozilla/"):
459 return 1
461 if self.inclname.endswith("-inl.h"):
462 return 6
464 return 4
466 if self.inclname.endswith("inlines.h"):
467 return 5
469 return 3
471 def quote(self):
472 if self.is_system:
473 return "<" + self.inclname + ">"
474 else:
475 return '"' + self.inclname + '"'
477 def sort_key(self, enclosing_inclname):
478 return (self.section(enclosing_inclname), self.inclname.lower())
480 def to_source(self):
481 return self.include_prefix + self.quote() + self.line_suffix + "\n"
484 class CppBlock(object):
485 """C preprocessor block: a whole file or a single #if/#elif/#else block.
487 A #if/#endif block is the contents of a #if/#endif (or similar) section.
488 The top-level block, which is not within a #if/#endif pair, is also
489 considered a block.
491 Each kid is either an Include (representing a #include), OrdinaryCode, or
492 a nested CppBlock."""
494 def __init__(self, start_line=""):
495 self.start = start_line
496 self.end = ""
497 self.kids = []
499 def is_style_relevant(self):
500 return True
502 def append_ordinary_line(self, line):
503 if len(self.kids) == 0 or not isinstance(self.kids[-1], OrdinaryCode):
504 self.kids.append(OrdinaryCode())
505 self.kids[-1].lines.append(line)
507 def style_relevant_kids(self):
508 """Return a list of kids in this block that are style-relevant."""
509 return [kid for kid in self.kids if kid.is_style_relevant()]
511 def sorted(self, enclosing_inclname):
512 """Return a hopefully-sorted copy of this block. Implements --fixup.
514 When in doubt, this leaves the code unchanged.
517 def pretty_sorted_includes(includes):
518 """Return a new list containing the given includes, in order,
519 with blank lines separating sections."""
520 keys = [inc.sort_key(enclosing_inclname) for inc in includes]
521 if sorted(keys) == keys:
522 return includes # if nothing is out of order, don't touch anything
524 output = []
525 current_section = None
526 for (section, _), inc in sorted(zip(keys, includes)):
527 if current_section is not None and section != current_section:
528 output.append(OrdinaryCode(["\n"])) # blank line
529 output.append(inc)
530 current_section = section
531 return output
533 def should_try_to_sort(includes):
534 if "tests/style/BadIncludes" in enclosing_inclname:
535 return False # don't straighten the counterexample
536 if any(inc.inclname in oddly_ordered_inclnames for inc in includes):
537 return False # don't sort batches containing odd includes
538 if includes == sorted(
539 includes, key=lambda inc: inc.sort_key(enclosing_inclname)
541 return False # it's already sorted, avoid whitespace-only fixups
542 return True
544 # The content of the eventual output of this method.
545 output = []
547 # The current batch of includes to sort. This list only ever contains Include objects
548 # and whitespace OrdinaryCode objects.
549 batch = []
551 def flush_batch():
552 """Sort the contents of `batch` and move it to `output`."""
554 assert all(
555 isinstance(item, Include)
556 or (isinstance(item, OrdinaryCode) and "".join(item.lines).isspace())
557 for item in batch
560 # Here we throw away the blank lines.
561 # `pretty_sorted_includes` puts them back.
562 includes = []
563 last_include_index = -1
564 for i, item in enumerate(batch):
565 if isinstance(item, Include):
566 includes.append(item)
567 last_include_index = i
568 cutoff = last_include_index + 1
570 if should_try_to_sort(includes):
571 output.extend(pretty_sorted_includes(includes) + batch[cutoff:])
572 else:
573 output.extend(batch)
574 del batch[:]
576 for kid in self.kids:
577 if isinstance(kid, CppBlock):
578 flush_batch()
579 output.append(kid.sorted(enclosing_inclname))
580 elif isinstance(kid, Include):
581 batch.append(kid)
582 else:
583 assert isinstance(kid, OrdinaryCode)
584 if kid.to_source().isspace():
585 batch.append(kid)
586 else:
587 flush_batch()
588 output.append(kid)
589 flush_batch()
591 result = CppBlock()
592 result.start = self.start
593 result.end = self.end
594 result.kids = output
595 return result
597 def to_source(self):
598 return self.start + "".join(kid.to_source() for kid in self.kids) + self.end
601 class OrdinaryCode(object):
602 """A list of lines of code that aren't #include/#if/#else/#endif lines."""
604 def __init__(self, lines=None):
605 self.lines = lines if lines is not None else []
607 def is_style_relevant(self):
608 return False
610 def to_source(self):
611 return "".join(self.lines)
614 # A "snippet" is one of:
616 # * Include - representing an #include line
617 # * CppBlock - a whole file or #if/#elif/#else block; contains a list of snippets
618 # * OrdinaryCode - representing lines of non-#include-relevant code
621 def read_file(f):
622 block_stack = [CppBlock()]
624 # Extract the #include statements as a tree of snippets.
625 for linenum, line in enumerate(f, start=1):
626 if line.lstrip().startswith("#"):
627 # Look for a |#include "..."| line.
628 m = re.match(r'(\s*#\s*include\s+)"([^"]*)"(.*)', line)
629 if m is not None:
630 prefix, inclname, suffix = m.groups()
631 block_stack[-1].kids.append(
632 Include(prefix, inclname, suffix, linenum, is_system=False)
634 continue
636 # Look for a |#include <...>| line.
637 m = re.match(r"(\s*#\s*include\s+)<([^>]*)>(.*)", line)
638 if m is not None:
639 prefix, inclname, suffix = m.groups()
640 block_stack[-1].kids.append(
641 Include(prefix, inclname, suffix, linenum, is_system=True)
643 continue
645 # Look for a |#{if,ifdef,ifndef}| line.
646 m = re.match(r"\s*#\s*(if|ifdef|ifndef)\b", line)
647 if m is not None:
648 # Open a new block.
649 new_block = CppBlock(line)
650 block_stack[-1].kids.append(new_block)
651 block_stack.append(new_block)
652 continue
654 # Look for a |#{elif,else}| line.
655 m = re.match(r"\s*#\s*(elif|else)\b", line)
656 if m is not None:
657 # Close the current block, and open an adjacent one.
658 block_stack.pop()
659 new_block = CppBlock(line)
660 block_stack[-1].kids.append(new_block)
661 block_stack.append(new_block)
662 continue
664 # Look for a |#endif| line.
665 m = re.match(r"\s*#\s*endif\b", line)
666 if m is not None:
667 # Close the current block.
668 block_stack.pop().end = line
669 if len(block_stack) == 0:
670 raise ValueError("#endif without #if at line " + str(linenum))
671 continue
673 # Otherwise, we have an ordinary line.
674 block_stack[-1].append_ordinary_line(line)
676 if len(block_stack) > 1:
677 raise ValueError("unmatched #if")
678 return block_stack[-1]
681 def check_file(
682 filename, inclname, file_kind, code, all_inclnames, included_h_inclnames
684 def check_include_statement(include):
685 """Check the style of a single #include statement."""
687 if include.is_system:
688 # Check it is not a known local file (in which case it's probably a system header).
689 if (
690 include.inclname in included_inclnames_to_ignore
691 or include.inclname in all_inclnames
693 error(
694 filename,
695 include.linenum,
696 include.quote() + " should be included using",
697 'the #include "..." form',
700 else:
701 msg = deprecated_inclnames.get(include.inclname)
702 if msg:
703 error(
704 filename,
705 include.linenum,
706 include.quote() + " is deprecated: " + msg,
709 if file_kind == FileKind.H or file_kind == FileKind.INL_H:
710 msg = deprecated_inclnames_in_header.get(include.inclname)
711 if msg and filename not in deprecated_inclnames_in_header_excludes:
712 error(
713 filename,
714 include.linenum,
715 include.quote() + " is deprecated: " + msg,
718 if include.inclname not in included_inclnames_to_ignore:
719 included_kind = FileKind.get(include.inclname)
721 # Check the #include path has the correct form.
722 if include.inclname not in all_inclnames:
723 error(
724 filename,
725 include.linenum,
726 include.quote() + " is included using the wrong path;",
727 "did you forget a prefix, or is the file not yet committed?",
730 # Record inclusions of .h files for cycle detection later.
731 # (Exclude .tbl and .msg files.)
732 elif included_kind == FileKind.H or included_kind == FileKind.INL_H:
733 included_h_inclnames.add(include.inclname)
735 # Check a H file doesn't #include an INL_H file.
736 if file_kind == FileKind.H and included_kind == FileKind.INL_H:
737 error(
738 filename,
739 include.linenum,
740 "vanilla header includes an inline-header file "
741 + include.quote(),
744 # Check a file doesn't #include itself. (We do this here because the cycle
745 # detection below doesn't detect this case.)
746 if inclname == include.inclname:
747 error(filename, include.linenum, "the file includes itself")
749 def check_includes_order(include1, include2):
750 """Check the ordering of two #include statements."""
752 if (
753 include1.inclname in oddly_ordered_inclnames
754 or include2.inclname in oddly_ordered_inclnames
756 return
758 section1 = include1.section(inclname)
759 section2 = include2.section(inclname)
760 if (section1 > section2) or (
761 (section1 == section2)
762 and (include1.inclname.lower() > include2.inclname.lower())
764 error(
765 filename,
766 str(include1.linenum) + ":" + str(include2.linenum),
767 include1.quote() + " should be included after " + include2.quote(),
770 # Check the extracted #include statements, both individually, and the ordering of
771 # adjacent pairs that live in the same block.
772 def pair_traverse(prev, this):
773 if isinstance(this, Include):
774 check_include_statement(this)
775 if isinstance(prev, Include):
776 check_includes_order(prev, this)
777 else:
778 kids = this.style_relevant_kids()
779 for prev2, this2 in zip([None] + kids[0:-1], kids):
780 pair_traverse(prev2, this2)
782 pair_traverse(None, code)
785 def find_cycles(all_inclnames, edges):
786 """Find and draw any cycles."""
788 SCCs = tarjan(all_inclnames, edges)
790 # The various sorted() calls below ensure the output is deterministic.
792 def draw_SCC(c):
793 cset = set(c)
794 drawn = set()
796 def draw(v, indent):
797 out(" " * indent + ("-> " if indent else " ") + v)
798 if v in drawn:
799 return
800 drawn.add(v)
801 for succ in sorted(edges[v]):
802 if succ in cset:
803 draw(succ, indent + 1)
805 draw(sorted(c)[0], 0)
806 out("")
808 have_drawn_an_SCC = False
809 for scc in sorted(SCCs):
810 if len(scc) != 1:
811 if not have_drawn_an_SCC:
812 error("(multiple files)", None, "header files form one or more cycles")
813 have_drawn_an_SCC = True
815 draw_SCC(scc)
818 # Tarjan's algorithm for finding the strongly connected components (SCCs) of a graph.
819 # https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
820 def tarjan(V, E):
821 vertex_index = {}
822 vertex_lowlink = {}
823 index = 0
824 S = []
825 all_SCCs = []
827 def strongconnect(v, index):
828 # Set the depth index for v to the smallest unused index
829 vertex_index[v] = index
830 vertex_lowlink[v] = index
831 index += 1
832 S.append(v)
834 # Consider successors of v
835 for w in E[v]:
836 if w not in vertex_index:
837 # Successor w has not yet been visited; recurse on it
838 index = strongconnect(w, index)
839 vertex_lowlink[v] = min(vertex_lowlink[v], vertex_lowlink[w])
840 elif w in S:
841 # Successor w is in stack S and hence in the current SCC
842 vertex_lowlink[v] = min(vertex_lowlink[v], vertex_index[w])
844 # If v is a root node, pop the stack and generate an SCC
845 if vertex_lowlink[v] == vertex_index[v]:
846 i = S.index(v)
847 scc = S[i:]
848 del S[i:]
849 all_SCCs.append(scc)
851 return index
853 for v in V:
854 if v not in vertex_index:
855 index = strongconnect(v, index)
857 return all_SCCs
860 def main():
861 if sys.argv[1:] == ["--fixup"]:
862 # Sort #include directives in-place. Fixup mode doesn't solve
863 # all possible silliness that the script checks for; it's just a
864 # hack for the common case where renaming a header causes style
865 # errors.
866 fixup = True
867 elif sys.argv[1:] == []:
868 fixup = False
869 else:
870 print(
871 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | unexpected command "
872 "line options: " + repr(sys.argv[1:])
874 sys.exit(1)
876 ok = check_style(fixup)
878 if ok:
879 print("TEST-PASS | check_spidermonkey_style.py | ok")
880 else:
881 print(
882 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | "
883 + "actual output does not match expected output; diff is above."
885 print(
886 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | "
887 + "Hint: If the problem is that you renamed a header, and many #includes "
888 + "are no longer in alphabetical order, commit your work and then try "
889 + "`check_spidermonkey_style.py --fixup`. "
890 + "You need to commit first because --fixup modifies your files in place."
893 sys.exit(0 if ok else 1)
896 if __name__ == "__main__":
897 main()