Home | History | Annotate | Download | only in pdfium
      1 # Copyright 2015 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 
      5 """Presubmit script for pdfium.
      6 
      7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
      8 for more details about the presubmit API built into depot_tools.
      9 """
     10 
     11 LINT_FILTERS = [
     12   # Rvalue ref checks are unreliable.
     13   '-build/c++11',
     14   # Need to fix header names not matching cpp names.
     15   '-build/include_order',
     16   # Too many to fix at the moment.
     17   '-readability/casting',
     18   # Need to refactor large methods to fix.
     19   '-readability/fn_size',
     20   # Lots of usage to fix first.
     21   '-runtime/int',
     22   # Lots of non-const references need to be fixed
     23   '-runtime/references',
     24   # We are not thread safe, so this will never pass.
     25   '-runtime/threadsafe_fn',
     26   # Figure out how to deal with #defines that git cl format creates.
     27   '-whitespace/indent',
     28 ]
     29 
     30 
     31 _INCLUDE_ORDER_WARNING = (
     32     'Your #include order seems to be broken. Remember to use the right '
     33     'collation (LC_COLLATE=C) and check\nhttps://google.github.io/styleguide/'
     34     'cppguide.html#Names_and_Order_of_Includes')
     35 
     36 
     37 def _CheckUnwantedDependencies(input_api, output_api):
     38   """Runs checkdeps on #include statements added in this
     39   change. Breaking - rules is an error, breaking ! rules is a
     40   warning.
     41   """
     42   import sys
     43   # We need to wait until we have an input_api object and use this
     44   # roundabout construct to import checkdeps because this file is
     45   # eval-ed and thus doesn't have __file__.
     46   original_sys_path = sys.path
     47   try:
     48     sys.path = sys.path + [input_api.os_path.join(
     49         input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
     50     import checkdeps
     51     from cpp_checker import CppChecker
     52     from rules import Rule
     53   except ImportError:
     54     return [output_api.PresubmitError(
     55         'Unable to run checkdeps, does pdfium/buildtools/checkdeps exist?')]
     56   finally:
     57     # Restore sys.path to what it was before.
     58     sys.path = original_sys_path
     59 
     60   added_includes = []
     61   for f in input_api.AffectedFiles():
     62     if not CppChecker.IsCppFile(f.LocalPath()):
     63       continue
     64 
     65     changed_lines = [line for line_num, line in f.ChangedContents()]
     66     added_includes.append([f.LocalPath(), changed_lines])
     67 
     68   deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
     69 
     70   error_descriptions = []
     71   warning_descriptions = []
     72   for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
     73       added_includes):
     74     description_with_path = '%s\n    %s' % (path, rule_description)
     75     if rule_type == Rule.DISALLOW:
     76       error_descriptions.append(description_with_path)
     77     else:
     78       warning_descriptions.append(description_with_path)
     79 
     80   results = []
     81   if error_descriptions:
     82     results.append(output_api.PresubmitError(
     83         'You added one or more #includes that violate checkdeps rules.',
     84         error_descriptions))
     85   if warning_descriptions:
     86     results.append(output_api.PresubmitPromptOrNotify(
     87         'You added one or more #includes of files that are temporarily\n'
     88         'allowed but being removed. Can you avoid introducing the\n'
     89         '#include? See relevant DEPS file(s) for details and contacts.',
     90         warning_descriptions))
     91   return results
     92 
     93 
     94 def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
     95   """Checks that the lines in scope occur in the right order.
     96 
     97   1. C system files in alphabetical order
     98   2. C++ system files in alphabetical order
     99   3. Project's .h files
    100   """
    101 
    102   c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
    103   cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
    104   custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
    105 
    106   C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
    107 
    108   state = C_SYSTEM_INCLUDES
    109 
    110   previous_line = ''
    111   previous_line_num = 0
    112   problem_linenums = []
    113   out_of_order = " - line belongs before previous line"
    114   for line_num, line in scope:
    115     if c_system_include_pattern.match(line):
    116       if state != C_SYSTEM_INCLUDES:
    117         problem_linenums.append((line_num, previous_line_num,
    118             " - C system include file in wrong block"))
    119       elif previous_line and previous_line > line:
    120         problem_linenums.append((line_num, previous_line_num,
    121             out_of_order))
    122     elif cpp_system_include_pattern.match(line):
    123       if state == C_SYSTEM_INCLUDES:
    124         state = CPP_SYSTEM_INCLUDES
    125       elif state == CUSTOM_INCLUDES:
    126         problem_linenums.append((line_num, previous_line_num,
    127             " - c++ system include file in wrong block"))
    128       elif previous_line and previous_line > line:
    129         problem_linenums.append((line_num, previous_line_num, out_of_order))
    130     elif custom_include_pattern.match(line):
    131       if state != CUSTOM_INCLUDES:
    132         state = CUSTOM_INCLUDES
    133       elif previous_line and previous_line > line:
    134         problem_linenums.append((line_num, previous_line_num, out_of_order))
    135     else:
    136       problem_linenums.append((line_num, previous_line_num,
    137           "Unknown include type"))
    138     previous_line = line
    139     previous_line_num = line_num
    140 
    141   warnings = []
    142   for (line_num, previous_line_num, failure_type) in problem_linenums:
    143     if line_num in changed_linenums or previous_line_num in changed_linenums:
    144       warnings.append('    %s:%d:%s' % (file_path, line_num, failure_type))
    145   return warnings
    146 
    147 
    148 def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
    149   """Checks the #include order for the given file f."""
    150 
    151   system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
    152   # Exclude the following includes from the check:
    153   # 1) #include <.../...>, e.g., <sys/...> includes often need to appear in a
    154   # specific order.
    155   # 2) <atlbase.h>, "build/build_config.h"
    156   excluded_include_pattern = input_api.re.compile(
    157       r'\s*#include (\<.*/.*|\<atlbase\.h\>|"build/build_config.h")')
    158   custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
    159   # Match the final or penultimate token if it is xxxtest so we can ignore it
    160   # when considering the special first include.
    161   test_file_tag_pattern = input_api.re.compile(
    162     r'_[a-z]+test(?=(_[a-zA-Z0-9]+)?\.)')
    163   if_pattern = input_api.re.compile(
    164       r'\s*#\s*(if|elif|else|endif|define|undef).*')
    165   # Some files need specialized order of includes; exclude such files from this
    166   # check.
    167   uncheckable_includes_pattern = input_api.re.compile(
    168       r'\s*#include '
    169       '("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
    170 
    171   contents = f.NewContents()
    172   warnings = []
    173   line_num = 0
    174 
    175   # Handle the special first include. If the first include file is
    176   # some/path/file.h, the corresponding including file can be some/path/file.cc,
    177   # some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
    178   # etc. It's also possible that no special first include exists.
    179   # If the included file is some/path/file_platform.h the including file could
    180   # also be some/path/file_xxxtest_platform.h.
    181   including_file_base_name = test_file_tag_pattern.sub(
    182     '', input_api.os_path.basename(f.LocalPath()))
    183 
    184   for line in contents:
    185     line_num += 1
    186     if system_include_pattern.match(line):
    187       # No special first include -> process the line again along with normal
    188       # includes.
    189       line_num -= 1
    190       break
    191     match = custom_include_pattern.match(line)
    192     if match:
    193       match_dict = match.groupdict()
    194       header_basename = test_file_tag_pattern.sub(
    195         '', input_api.os_path.basename(match_dict['FILE'])).replace('.h', '')
    196 
    197       if header_basename not in including_file_base_name:
    198         # No special first include -> process the line again along with normal
    199         # includes.
    200         line_num -= 1
    201       break
    202 
    203   # Split into scopes: Each region between #if and #endif is its own scope.
    204   scopes = []
    205   current_scope = []
    206   for line in contents[line_num:]:
    207     line_num += 1
    208     if uncheckable_includes_pattern.match(line):
    209       continue
    210     if if_pattern.match(line):
    211       scopes.append(current_scope)
    212       current_scope = []
    213     elif ((system_include_pattern.match(line) or
    214            custom_include_pattern.match(line)) and
    215           not excluded_include_pattern.match(line)):
    216       current_scope.append((line_num, line))
    217   scopes.append(current_scope)
    218 
    219   for scope in scopes:
    220     warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
    221                                                changed_linenums))
    222   return warnings
    223 
    224 
    225 def _CheckIncludeOrder(input_api, output_api):
    226   """Checks that the #include order is correct.
    227 
    228   1. The corresponding header for source files.
    229   2. C system files in alphabetical order
    230   3. C++ system files in alphabetical order
    231   4. Project's .h files in alphabetical order
    232 
    233   Each region separated by #if, #elif, #else, #endif, #define and #undef follows
    234   these rules separately.
    235   """
    236   def FileFilterIncludeOrder(affected_file):
    237     black_list = (input_api.DEFAULT_BLACK_LIST)
    238     return input_api.FilterSourceFile(affected_file, black_list=black_list)
    239 
    240   warnings = []
    241   for f in input_api.AffectedFiles(file_filter=FileFilterIncludeOrder):
    242     if f.LocalPath().endswith(('.cc', '.cpp', '.h', '.mm')):
    243       changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
    244       warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
    245 
    246   results = []
    247   if warnings:
    248     results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
    249                                                       warnings))
    250   return results
    251 
    252 
    253 def CheckChangeOnUpload(input_api, output_api):
    254   results = []
    255   results += _CheckUnwantedDependencies(input_api, output_api)
    256   results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
    257   results += input_api.canned_checks.CheckChangeLintsClean(
    258       input_api, output_api, None, LINT_FILTERS)
    259   results += _CheckIncludeOrder(input_api, output_api)
    260 
    261   return results
    262