summaryrefslogtreecommitdiff
path: root/chromium/buildtools/checkdeps/cpp_checker.py
blob: 3efad9741c992121b41bb5ade7b6393f210707c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Checks C++ and Objective-C files for illegal includes."""

import codecs
import os
import re

import results
from rules import Rule, MessageRule


class CppChecker(object):

  EXTENSIONS = [
      '.h',
      '.cc',
      '.cpp',
      '.m',
      '.mm',
  ]

  # The maximum number of non-include lines we can see before giving up.
  _MAX_UNINTERESTING_LINES = 50

  # The maximum line length, this is to be efficient in the case of very long
  # lines (which can't be #includes).
  _MAX_LINE_LENGTH = 128

  # This regular expression will be used to extract filenames from include
  # statements.
  _EXTRACT_INCLUDE_PATH = re.compile(
      '[ \t]*#[ \t]*(?:include|import)[ \t]+"(.*)"')

  def __init__(self, verbose, resolve_dotdot=False, root_dir=''):
    self._verbose = verbose
    self._resolve_dotdot = resolve_dotdot
    self._root_dir = root_dir

  def CheckLine(self, rules, line, dependee_path, fail_on_temp_allow=False):
    """Checks the given line with the given rule set.

    Returns a tuple (is_include, dependency_violation) where
    is_include is True only if the line is an #include or #import
    statement, and dependency_violation is an instance of
    results.DependencyViolation if the line violates a rule, or None
    if it does not.
    """
    found_item = self._EXTRACT_INCLUDE_PATH.match(line)
    if not found_item:
      return False, None  # Not a match

    include_path = found_item.group(1)

    if '\\' in include_path:
      return True, results.DependencyViolation(
          include_path,
          MessageRule('Include paths may not include backslashes.'),
          rules)

    if '/' not in include_path:
      # Don't fail when no directory is specified. We may want to be more
      # strict about this in the future.
      if self._verbose:
        print ' WARNING: include specified with no directory: ' + include_path
      return True, None

    if self._resolve_dotdot and '../' in include_path:
      dependee_dir = os.path.dirname(dependee_path)
      include_path = os.path.join(dependee_dir, include_path)
      include_path = os.path.relpath(include_path, self._root_dir)

    rule = rules.RuleApplyingTo(include_path, dependee_path)
    if (rule.allow == Rule.DISALLOW or
        (fail_on_temp_allow and rule.allow == Rule.TEMP_ALLOW)):
      return True, results.DependencyViolation(include_path, rule, rules)
    return True, None

  def CheckFile(self, rules, filepath):
    if self._verbose:
      print 'Checking: ' + filepath

    dependee_status = results.DependeeStatus(filepath)
    ret_val = ''  # We'll collect the error messages in here
    last_include = 0
    with codecs.open(filepath, encoding='utf-8') as f:
      in_if0 = 0
      for line_num, line in enumerate(f):
        if line_num - last_include > self._MAX_UNINTERESTING_LINES:
          break

        line = line.strip()

        # Check to see if we're at / inside an #if 0 block
        if line.startswith('#if 0'):
          in_if0 += 1
          continue
        if in_if0 > 0:
          if line.startswith('#if'):
            in_if0 += 1
          elif line.startswith('#endif'):
            in_if0 -= 1
          continue

        is_include, violation = self.CheckLine(rules, line, filepath)
        if is_include:
          last_include = line_num
        if violation:
          dependee_status.AddViolation(violation)

    return dependee_status

  @staticmethod
  def IsCppFile(file_path):
    """Returns True iff the given path ends in one of the extensions
    handled by this checker.
    """
    return os.path.splitext(file_path)[1] in CppChecker.EXTENSIONS

  def ShouldCheck(self, file_path):
    """Check if the new #include file path should be presubmit checked.

    Args:
      file_path: file path to be checked

    Return:
      bool: True if the file should be checked; False otherwise.
    """
    return self.IsCppFile(file_path)