[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 1 | # Copyright 2013 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 | """Top-level presubmit script for Chromium media component. |
| 6 | |
| 7 | See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts |
tfarina | 78bb92f4 | 2015-01-31 00:20:48 | [diff] [blame] | 8 | for more details about the presubmit API built into depot_tools. |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 9 | """ |
| 10 | |
mcasas | e60e4f7 | 2016-04-02 01:20:43 | [diff] [blame] | 11 | import re |
| 12 | import string |
| 13 | |
| 14 | # Well-defined simple classes containing only <= 4 ints, or <= 2 floats. |
| 15 | BASE_TIME_TYPES = [ |
| 16 | 'base::Time', |
| 17 | 'base::TimeDelta', |
| 18 | 'base::TimeTicks', |
| 19 | ] |
| 20 | |
| 21 | BASE_TIME_TYPES_RE = re.compile(r'\bconst (%s)&' % |
| 22 | string.join(BASE_TIME_TYPES, '|')) |
| 23 | |
[email protected] | 8c966dd | 2014-01-07 22:26:40 | [diff] [blame] | 24 | def _FilterFile(affected_file): |
| 25 | """Return true if the file could contain code requiring a presubmit check.""" |
| 26 | return affected_file.LocalPath().endswith( |
| 27 | ('.h', '.cc', '.cpp', '.cxx', '.mm')) |
| 28 | |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 29 | |
| 30 | def _CheckForUseOfWrongClock(input_api, output_api): |
| 31 | """Make sure new lines of media code don't use a clock susceptible to skew.""" |
| 32 | |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 33 | # Regular expression that should detect any explicit references to the |
| 34 | # base::Time type (or base::Clock/DefaultClock), whether in using decls, |
| 35 | # typedefs, or to call static methods. |
[email protected] | 49d2c70 | 2013-06-19 04:05:00 | [diff] [blame] | 36 | base_time_type_pattern = r'(^|\W)base::(Time|Clock|DefaultClock)(\W|$)' |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 37 | |
| 38 | # Regular expression that should detect references to the base::Time class |
[email protected] | 49d2c70 | 2013-06-19 04:05:00 | [diff] [blame] | 39 | # members, such as a call to base::Time::Now. |
| 40 | base_time_member_pattern = r'(^|\W)(Time|Clock|DefaultClock)::' |
| 41 | |
| 42 | # Regular expression to detect "using base::Time" declarations. We want to |
| 43 | # prevent these from triggerring a warning. For example, it's perfectly |
| 44 | # reasonable for code to be written like this: |
| 45 | # |
| 46 | # using base::Time; |
| 47 | # ... |
avi | 1323b9c2 | 2015-12-23 06:22:36 | [diff] [blame] | 48 | # int64_t foo_us = foo_s * Time::kMicrosecondsPerSecond; |
[email protected] | 49d2c70 | 2013-06-19 04:05:00 | [diff] [blame] | 49 | using_base_time_decl_pattern = r'^\s*using\s+(::)?base::Time\s*;' |
| 50 | |
| 51 | # Regular expression to detect references to the kXXX constants in the |
| 52 | # base::Time class. We want to prevent these from triggerring a warning. |
| 53 | base_time_konstant_pattern = r'(^|\W)Time::k\w+' |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 54 | |
| 55 | problem_re = input_api.re.compile( |
| 56 | r'(' + base_time_type_pattern + r')|(' + base_time_member_pattern + r')') |
[email protected] | 49d2c70 | 2013-06-19 04:05:00 | [diff] [blame] | 57 | exception_re = input_api.re.compile( |
| 58 | r'(' + using_base_time_decl_pattern + r')|(' + |
| 59 | base_time_konstant_pattern + r')') |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 60 | problems = [] |
[email protected] | 8c966dd | 2014-01-07 22:26:40 | [diff] [blame] | 61 | for f in input_api.AffectedSourceFiles(_FilterFile): |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 62 | for line_number, line in f.ChangedContents(): |
| 63 | if problem_re.search(line): |
[email protected] | 49d2c70 | 2013-06-19 04:05:00 | [diff] [blame] | 64 | if not exception_re.search(line): |
| 65 | problems.append( |
| 66 | ' %s:%d\n %s' % (f.LocalPath(), line_number, line.strip())) |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 67 | |
| 68 | if problems: |
| 69 | return [output_api.PresubmitPromptOrNotify( |
| 70 | 'You added one or more references to the base::Time class and/or one\n' |
| 71 | 'of its member functions (or base::Clock/DefaultClock). In media\n' |
| 72 | 'code, it is rarely correct to use a clock susceptible to time skew!\n' |
| 73 | 'Instead, could you use base::TimeTicks to track the passage of\n' |
| 74 | 'real-world time?\n\n' + |
| 75 | '\n'.join(problems))] |
| 76 | else: |
| 77 | return [] |
| 78 | |
| 79 | |
[email protected] | c8c0ca6 | 2014-02-28 20:30:44 | [diff] [blame] | 80 | def _CheckForHistogramOffByOne(input_api, output_api): |
| 81 | """Make sure histogram enum maxes are used properly""" |
| 82 | |
| 83 | # A general-purpose chunk of regex to match whitespace and/or comments |
| 84 | # that may be interspersed with the code we're interested in: |
| 85 | comment = r'/\*.*?\*/|//[^\n]*' |
| 86 | whitespace = r'(?:[\n\t ]|(?:' + comment + r'))*' |
| 87 | |
| 88 | # The name is assumed to be a literal string. |
| 89 | histogram_name = r'"[^"]*"' |
| 90 | |
| 91 | # This can be an arbitrary expression, so just ensure it isn't a ; to prevent |
| 92 | # matching past the end of this statement. |
| 93 | histogram_value = r'[^;]*' |
| 94 | |
| 95 | # In parens so we can retrieve it for further checks. |
| 96 | histogram_max = r'([^;,]*)' |
| 97 | |
| 98 | # This should match a uma histogram enumeration macro expression. |
| 99 | uma_macro_re = input_api.re.compile( |
| 100 | r'\bUMA_HISTOGRAM_ENUMERATION\(' + whitespace + histogram_name + r',' + |
| 101 | whitespace + histogram_value + r',' + whitespace + histogram_max + |
| 102 | whitespace + r'\)' + whitespace + r';(?:' + whitespace + |
| 103 | r'\/\/ (PRESUBMIT_IGNORE_UMA_MAX))?') |
| 104 | |
| 105 | uma_max_re = input_api.re.compile(r'.*(?:Max|MAX).* \+ 1') |
| 106 | |
| 107 | problems = [] |
| 108 | |
| 109 | for f in input_api.AffectedSourceFiles(_FilterFile): |
| 110 | contents = input_api.ReadFile(f) |
| 111 | |
| 112 | # We want to match across lines, but still report a line number, so we keep |
| 113 | # track of the line we're on as we search through the file. |
| 114 | line_number = 1 |
| 115 | |
| 116 | # We search the entire file, then check if any violations are in the changed |
| 117 | # areas, this is inefficient, but simple. A UMA_HISTOGRAM_ENUMERATION call |
| 118 | # will often span multiple lines, so finding a match looking just at the |
| 119 | # deltas line-by-line won't catch problems. |
| 120 | match = uma_macro_re.search(contents) |
| 121 | while match: |
| 122 | line_number += contents.count('\n', 0, match.start()) |
| 123 | max_arg = match.group(1) # The third argument. |
| 124 | |
| 125 | if (not uma_max_re.match(max_arg) and match.group(2) != |
| 126 | 'PRESUBMIT_IGNORE_UMA_MAX'): |
| 127 | uma_range = range(match.start(), match.end() + 1) |
| 128 | # Check if any part of the match is in the changed lines: |
| 129 | for num, line in f.ChangedContents(): |
| 130 | if line_number <= num <= line_number + match.group().count('\n'): |
| 131 | problems.append('%s:%d' % (f, line_number)) |
| 132 | break |
| 133 | |
| 134 | # Strip off the file contents up to the end of the match and update the |
| 135 | # line number. |
| 136 | contents = contents[match.end():] |
| 137 | line_number += match.group().count('\n') |
| 138 | match = uma_macro_re.search(contents) |
| 139 | |
| 140 | if problems: |
| 141 | return [output_api.PresubmitError( |
| 142 | 'UMA_HISTOGRAM_ENUMERATION reports in src/media/ are expected to adhere\n' |
| 143 | 'to the following guidelines:\n' |
| 144 | ' - The max value (3rd argument) should be an enum value equal to the\n' |
| 145 | ' last valid value, e.g. FOO_MAX = LAST_VALID_FOO.\n' |
| 146 | ' - 1 must be added to that max value.\n' |
| 147 | 'Contact [email protected] if you have questions.' , problems)] |
| 148 | |
| 149 | return [] |
| 150 | |
| 151 | |
mcasas | e60e4f7 | 2016-04-02 01:20:43 | [diff] [blame] | 152 | def _CheckPassByValue(input_api, output_api): |
| 153 | """Check that base::Time and derived classes are passed by value, and not by |
| 154 | const reference """ |
| 155 | |
| 156 | problems = [] |
| 157 | |
| 158 | for f in input_api.AffectedSourceFiles(_FilterFile): |
| 159 | for line_number, line in f.ChangedContents(): |
| 160 | if BASE_TIME_TYPES_RE.search(line): |
| 161 | problems.append('%s:%d' % (f, line_number)) |
| 162 | |
| 163 | if problems: |
| 164 | return [output_api.PresubmitError( |
| 165 | 'base::Time and derived classes should be passed by value and not by\n' |
| 166 | 'const ref, see base/time/time.h for more information.', problems)] |
| 167 | return [] |
| 168 | |
| 169 | |
dalecurtis | c3af509 | 2017-02-11 02:08:18 | [diff] [blame] | 170 | def _CheckForUseOfLazyInstance(input_api, output_api): |
| 171 | """Check that base::LazyInstance is not used.""" |
| 172 | |
| 173 | problems = [] |
| 174 | |
| 175 | lazy_instance_re = re.compile(r'(^|\W)base::LazyInstance<') |
| 176 | |
| 177 | for f in input_api.AffectedSourceFiles(_FilterFile): |
| 178 | for line_number, line in f.ChangedContents(): |
| 179 | if lazy_instance_re.search(line): |
| 180 | problems.append('%s:%d' % (f, line_number)) |
| 181 | |
| 182 | if problems: |
| 183 | return [output_api.PresubmitError( |
| 184 | 'base::LazyInstance is deprecated; use a thread safe static.', problems)] |
| 185 | return [] |
| 186 | |
| 187 | |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 188 | def _CheckChange(input_api, output_api): |
| 189 | results = [] |
| 190 | results.extend(_CheckForUseOfWrongClock(input_api, output_api)) |
mcasas | e60e4f7 | 2016-04-02 01:20:43 | [diff] [blame] | 191 | results.extend(_CheckPassByValue(input_api, output_api)) |
[email protected] | c8c0ca6 | 2014-02-28 20:30:44 | [diff] [blame] | 192 | results.extend(_CheckForHistogramOffByOne(input_api, output_api)) |
dalecurtis | c3af509 | 2017-02-11 02:08:18 | [diff] [blame] | 193 | results.extend(_CheckForUseOfLazyInstance(input_api, output_api)) |
[email protected] | 1bd38bd | 2013-06-13 23:20:45 | [diff] [blame] | 194 | return results |
| 195 | |
| 196 | |
| 197 | def CheckChangeOnUpload(input_api, output_api): |
| 198 | return _CheckChange(input_api, output_api) |
| 199 | |
| 200 | |
| 201 | def CheckChangeOnCommit(input_api, output_api): |
| 202 | return _CheckChange(input_api, output_api) |