cpp_checker.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Checks C++ and Objective-C files for illegal includes."""
  5. import codecs
  6. import os
  7. import re
  8. import results
  9. from rules import Rule, MessageRule
  10. class CppChecker(object):
  11. EXTENSIONS = [
  12. '.h',
  13. '.cc',
  14. '.cpp',
  15. '.m',
  16. '.mm',
  17. ]
  18. # The maximum number of non-include lines we can see before giving up.
  19. _MAX_UNINTERESTING_LINES = 50
  20. # The maximum line length, this is to be efficient in the case of very long
  21. # lines (which can't be #includes).
  22. _MAX_LINE_LENGTH = 128
  23. # This regular expression will be used to extract filenames from include
  24. # statements.
  25. _EXTRACT_INCLUDE_PATH = re.compile(
  26. r'[ \t]*#[ \t]*(?:include|import)[ \t]+"(.*)"')
  27. def __init__(self, verbose, resolve_dotdot=False, root_dir=''):
  28. self._verbose = verbose
  29. self._resolve_dotdot = resolve_dotdot
  30. self._root_dir = root_dir
  31. def CheckLine(self, rules, line, dependee_path, fail_on_temp_allow=False):
  32. """Checks the given line with the given rule set.
  33. Returns a tuple (is_include, dependency_violation) where
  34. is_include is True only if the line is an #include or #import
  35. statement, and dependency_violation is an instance of
  36. results.DependencyViolation if the line violates a rule, or None
  37. if it does not.
  38. """
  39. found_item = self._EXTRACT_INCLUDE_PATH.match(line)
  40. if not found_item:
  41. return False, None # Not a match
  42. include_path = found_item.group(1)
  43. if '\\' in include_path:
  44. return True, results.DependencyViolation(
  45. include_path,
  46. MessageRule('Include paths may not include backslashes.'),
  47. rules)
  48. if '/' not in include_path:
  49. # Don't fail when no directory is specified. We may want to be more
  50. # strict about this in the future.
  51. if self._verbose:
  52. print(' WARNING: include specified with no directory: ' + include_path)
  53. return True, None
  54. if self._resolve_dotdot and '../' in include_path:
  55. dependee_dir = os.path.dirname(dependee_path)
  56. include_path = os.path.join(dependee_dir, include_path)
  57. include_path = os.path.relpath(include_path, self._root_dir)
  58. rule = rules.RuleApplyingTo(include_path, dependee_path)
  59. if (rule.allow == Rule.DISALLOW or
  60. (fail_on_temp_allow and rule.allow == Rule.TEMP_ALLOW)):
  61. return True, results.DependencyViolation(include_path, rule, rules)
  62. return True, None
  63. def CheckFile(self, rules, filepath):
  64. if self._verbose:
  65. print('Checking: ' + filepath)
  66. dependee_status = results.DependeeStatus(filepath)
  67. ret_val = '' # We'll collect the error messages in here
  68. last_include = 0
  69. with codecs.open(filepath, encoding='utf-8') as f:
  70. in_if0 = 0
  71. for line_num, line in enumerate(f):
  72. if line_num - last_include > self._MAX_UNINTERESTING_LINES:
  73. break
  74. line = line.strip()
  75. # Check to see if we're at / inside an #if 0 block
  76. if line.startswith('#if 0'):
  77. in_if0 += 1
  78. continue
  79. if in_if0 > 0:
  80. if line.startswith('#if'):
  81. in_if0 += 1
  82. elif line.startswith('#endif'):
  83. in_if0 -= 1
  84. continue
  85. is_include, violation = self.CheckLine(rules, line, filepath)
  86. if is_include:
  87. last_include = line_num
  88. if violation:
  89. dependee_status.AddViolation(violation)
  90. return dependee_status
  91. @staticmethod
  92. def IsCppFile(file_path):
  93. """Returns True iff the given path ends in one of the extensions
  94. handled by this checker.
  95. """
  96. return os.path.splitext(file_path)[1] in CppChecker.EXTENSIONS
  97. def ShouldCheck(self, file_path):
  98. """Check if the new #include file path should be presubmit checked.
  99. Args:
  100. file_path: file path to be checked
  101. Return:
  102. bool: True if the file should be checked; False otherwise.
  103. """
  104. return self.IsCppFile(file_path)