diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-29 13:47:09 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-29 13:47:09 +0000 |
commit | 7b52844d03b7971ffd066a240d4263cd71f56410 (patch) | |
tree | 3283f637bf7d4cf46840932f09c51600b2bccbd6 | |
parent | 0f54b3300ea7902266ae501b604e0d916a6d8f0c (diff) | |
parent | 0522922843c6ea447d027e6b88a0b22eb6c652a4 (diff) | |
download | repohooks-androidx-core-core-splashscreen-release.tar.gz |
Snap for 8517803 from 0522922843c6ea447d027e6b88a0b22eb6c652a4 to androidx-core-core-splashscreen-releaseandroidx-core-core-splashscreen-release
Change-Id: Ia37c7c2f46a985a96fd43ac5a25377f297c5837d
-rwxr-xr-x | pre-upload.py | 59 | ||||
-rw-r--r-- | rh/config.py | 44 | ||||
-rwxr-xr-x | rh/config_test.py | 14 | ||||
-rwxr-xr-x | rh/config_unittest.py | 2 | ||||
-rw-r--r-- | rh/git.py | 22 | ||||
-rw-r--r-- | rh/hooks.py | 99 | ||||
-rwxr-xr-x | rh/hooks_unittest.py | 28 | ||||
-rw-r--r-- | rh/results.py | 5 | ||||
-rw-r--r-- | rh/shell.py | 10 | ||||
-rwxr-xr-x | rh/shell_unittest.py | 10 | ||||
-rw-r--r-- | rh/terminal.py | 22 | ||||
-rw-r--r-- | rh/utils.py | 41 | ||||
-rwxr-xr-x | rh/utils_unittest.py | 6 | ||||
-rwxr-xr-x | tools/android_test_mapping_format.py | 7 | ||||
-rwxr-xr-x | tools/android_test_mapping_format_unittest.py | 48 | ||||
-rwxr-xr-x | tools/checkpatch.pl | 1844 | ||||
-rwxr-xr-x | tools/checkpatch.pl-update | 4 | ||||
-rwxr-xr-x | tools/clang-format.py | 10 | ||||
-rw-r--r-- | tools/const_structs.checkpatch | 95 | ||||
-rwxr-xr-x | tools/google-java-format.py | 13 | ||||
-rwxr-xr-x | tools/pylint.py | 18 | ||||
-rw-r--r-- | tools/spelling.txt | 382 |
22 files changed, 2107 insertions, 676 deletions
diff --git a/pre-upload.py b/pre-upload.py index 7eb11b8..fa65310 100755 --- a/pre-upload.py +++ b/pre-upload.py @@ -94,7 +94,7 @@ class Output(object): commit: commit hash. commit_summary: commit summary. """ - status_line = '[%s %s] %s' % (self.COMMIT, commit[0:12], commit_summary) + status_line = f'[{self.COMMIT} {commit[0:12]}] {commit_summary}' rh.terminal.print_status_line(status_line, print_newline=True) self.hook_index = 1 @@ -106,8 +106,8 @@ class Output(object): """ self._curr_hook_name = hook_name self.hook_start_time = datetime.datetime.now() - status_line = '[%s %d/%d] %s' % (self.RUNNING, self.hook_index, - self.num_hooks, hook_name) + status_line = (f'[{self.RUNNING} {self.hook_index}/{self.num_hooks}] ' + f'{hook_name}') self.hook_index += 1 rh.terminal.print_status_line(status_line) @@ -115,11 +115,11 @@ class Output(object): """Finish processing any per-hook state.""" duration = datetime.datetime.now() - self.hook_start_time if duration >= self._SLOW_HOOK_DURATION: + d = rh.utils.timedelta_str(duration) self.hook_warning( - 'This hook took %s to finish which is fairly slow for ' + f'This hook took {d} to finish which is fairly slow for ' 'developers.\nPlease consider moving the check to the ' - 'server/CI system instead.' % - (rh.utils.timedelta_str(duration),)) + 'server/CI system instead.') def hook_error(self, error): """Print an error for a single hook. @@ -135,7 +135,7 @@ class Output(object): Args: warning: warning string. """ - status_line = '[%s] %s' % (self.WARNING, self._curr_hook_name) + status_line = f'[{self.WARNING}] {self._curr_hook_name}' rh.terminal.print_status_line(status_line, print_newline=True) print(warning, file=sys.stderr) @@ -146,19 +146,19 @@ class Output(object): header: A unique identifier for the source of this error. error: error string. """ - status_line = '[%s] %s' % (self.FAILED, header) + status_line = f'[{self.FAILED}] {header}' rh.terminal.print_status_line(status_line, print_newline=True) print(error, file=sys.stderr) self.success = False def finish(self): """Print summary for all the hooks.""" - status_line = '[%s] repohooks for %s %s in %s' % ( - self.PASSED if self.success else self.FAILED, - self.project_name, - 'passed' if self.success else 'failed', - rh.utils.timedelta_str(datetime.datetime.now() - self.start_time)) - rh.terminal.print_status_line(status_line, print_newline=True) + header = self.PASSED if self.success else self.FAILED + status = 'passed' if self.success else 'failed' + d = rh.utils.timedelta_str(datetime.datetime.now() - self.start_time) + rh.terminal.print_status_line( + f'[{header}] repohooks for {self.project_name} {status} in {d}', + print_newline=True) def _process_hook_results(results): @@ -185,9 +185,9 @@ def _process_hook_results(results): if result: ret = '' if result.files: - ret += ' FILES: %s' % (result.files,) + ret += f' FILES: {result.files}' lines = result.error.splitlines() - ret += '\n'.join(' %s' % (x,) for x in lines) + ret += '\n'.join(f' {x}' for x in lines) if result.is_warning(): has_warning = True warning_ret += ret @@ -233,15 +233,15 @@ def _attempt_fixes(fixup_func_list, commit_list): # merge conflict resolution). Refuse to run the fix in those cases. return - prompt = ('An automatic fix can be attempted for the "%s" hook. ' - 'Do you want to run it?' % hook_name) + prompt = (f'An automatic fix can be attempted for the "{hook_name}" hook. ' + 'Do you want to run it?') if not rh.terminal.boolean_prompt(prompt): return result = fixup_func() if result: - print('Attempt to fix "%s" for commit "%s" failed: %s' % - (hook_name, commit, result), + print(f'Attempt to fix "{hook_name}" for commit "{commit}" failed: ' + f'{result}', file=sys.stderr) else: print('Fix successfully applied. Amend the current commit before ' @@ -281,8 +281,7 @@ def _run_project_hooks_in_cwd(project_name, proj_dir, output, commit_list=None): upstream_branch = rh.git.get_upstream_branch() except rh.utils.CalledProcessError as e: output.error('Upstream remote/tracking branch lookup', - '%s\nDid you run repo start? Is your HEAD detached?' % - (e,)) + f'{e}\nDid you run repo start? Is your HEAD detached?') return False project = rh.Project(name=project_name, dir=proj_dir, remote=remote) @@ -358,11 +357,11 @@ def _run_project_hooks(project_name, proj_dir=None, commit_list=None): result = rh.utils.run(cmd, capture_output=True) proj_dirs = result.stdout.split() if not proj_dirs: - print('%s cannot be found.' % project_name, file=sys.stderr) + print(f'{project_name} cannot be found.', file=sys.stderr) print('Please specify a valid project.', file=sys.stderr) return False if len(proj_dirs) > 1: - print('%s is associated with multiple directories.' % project_name, + print(f'{project_name} is associated with multiple directories.', file=sys.stderr) print('Please specify a directory to help disambiguate.', file=sys.stderr) @@ -409,9 +408,9 @@ def main(project_list, worktree_list=None, **_kwargs): if found_error: color = rh.terminal.Color() - print('%s: Preupload failed due to above error(s).\n' - 'For more info, please see:\n%s' % - (color.color(color.RED, 'FATAL'), REPOHOOKS_URL), + print(color.color(color.RED, 'FATAL') + + ': Preupload failed due to above error(s).\n' + f'For more info, please see:\n{REPOHOOKS_URL}', file=sys.stderr) sys.exit(1) @@ -461,16 +460,16 @@ def direct_main(argv): parser.error('The current directory is not part of a git project.') opts.dir = os.path.dirname(os.path.abspath(git_dir)) elif not os.path.isdir(opts.dir): - parser.error('Invalid dir: %s' % opts.dir) + parser.error(f'Invalid dir: {opts.dir}') elif not rh.git.is_git_repository(opts.dir): - parser.error('Not a git repository: %s' % opts.dir) + parser.error(f'Not a git repository: {opts.dir}') # Identify the project if it wasn't specified; this _requires_ the repo # tool to be installed and for the project to be part of a repo checkout. if not opts.project: opts.project = _identify_project(opts.dir) if not opts.project: - parser.error("Repo couldn't identify the project of %s" % opts.dir) + parser.error(f"Repo couldn't identify the project of {opts.dir}") if _run_project_hooks(opts.project, proj_dir=opts.dir, commit_list=opts.commits): diff --git a/rh/config.py b/rh/config.py index 1eb93a7..6cd218b 100644 --- a/rh/config.py +++ b/rh/config.py @@ -180,14 +180,14 @@ class PreUploadConfig(object): # Reject unknown sections. bad_sections = set(config.sections()) - self.VALID_SECTIONS if bad_sections: - raise ValidationError('%s: unknown sections: %s' % - (self.source, bad_sections)) + raise ValidationError( + f'{self.source}: unknown sections: {bad_sections}') # Reject blank custom hooks. for hook in self.custom_hooks: if not config.get(self.CUSTOM_HOOKS_SECTION, hook): - raise ValidationError('%s: custom hook "%s" cannot be blank' % - (self.source, hook)) + raise ValidationError( + f'{self.source}: custom hook "{hook}" cannot be blank') # Reject unknown builtin hooks. valid_builtin_hooks = set(rh.hooks.BUILTIN_HOOKS.keys()) @@ -195,8 +195,8 @@ class PreUploadConfig(object): hooks = set(config.options(self.BUILTIN_HOOKS_SECTION)) bad_hooks = hooks - valid_builtin_hooks if bad_hooks: - raise ValidationError('%s: unknown builtin hooks: %s' % - (self.source, bad_hooks)) + raise ValidationError( + f'{self.source}: unknown builtin hooks: {bad_hooks}') elif config.has_section(self.BUILTIN_HOOKS_OPTIONS_SECTION): raise ValidationError('Builtin hook options specified, but missing ' 'builtin hook settings') @@ -205,24 +205,26 @@ class PreUploadConfig(object): hooks = set(config.options(self.BUILTIN_HOOKS_OPTIONS_SECTION)) bad_hooks = hooks - valid_builtin_hooks if bad_hooks: - raise ValidationError('%s: unknown builtin hook options: %s' % - (self.source, bad_hooks)) + raise ValidationError( + f'{self.source}: unknown builtin hook options: {bad_hooks}') # Verify hooks are valid shell strings. for hook in self.custom_hooks: try: self.custom_hook(hook) except ValueError as e: - raise ValidationError('%s: hook "%s" command line is invalid: ' - '%s' % (self.source, hook, e)) from e + raise ValidationError( + f'{self.source}: hook "{hook}" command line is invalid: {e}' + ) from e # Verify hook options are valid shell strings. for hook in self.builtin_hooks: try: self.builtin_hook_option(hook) except ValueError as e: - raise ValidationError('%s: hook options "%s" are invalid: %s' % - (self.source, hook, e)) from e + raise ValidationError( + f'{self.source}: hook options "{hook}" are invalid: {e}' + ) from e # Reject unknown tools. valid_tools = set(rh.hooks.TOOL_PATHS.keys()) @@ -230,16 +232,16 @@ class PreUploadConfig(object): tools = set(config.options(self.TOOL_PATHS_SECTION)) bad_tools = tools - valid_tools if bad_tools: - raise ValidationError('%s: unknown tools: %s' % - (self.source, bad_tools)) + raise ValidationError( + f'{self.source}: unknown tools: {bad_tools}') # Reject unknown options. if config.has_section(self.OPTIONS_SECTION): options = set(config.options(self.OPTIONS_SECTION)) bad_options = options - self.VALID_OPTIONS if bad_options: - raise ValidationError('%s: unknown options: %s' % - (self.source, bad_options)) + raise ValidationError( + f'{self.source}: unknown options: {bad_options}') class PreUploadFile(PreUploadConfig): @@ -265,7 +267,7 @@ class PreUploadFile(PreUploadConfig): try: self.config.read(path) except configparser.ParsingError as e: - raise ValidationError('%s: %s' % (path, e)) from e + raise ValidationError(f'{path}: {e}') from e self._validate() @@ -294,9 +296,9 @@ class LocalPreUploadFile(PreUploadFile): # Reject Exclude Paths section for local config. if self.config.has_section(self.BUILTIN_HOOKS_EXCLUDE_SECTION): - raise ValidationError('%s: [%s] is not valid in local files' % - (self.path, - self.BUILTIN_HOOKS_EXCLUDE_SECTION)) + raise ValidationError( + f'{self.path}: [{self.BUILTIN_HOOKS_EXCLUDE_SECTION}] is not ' + 'valid in local files') class GlobalPreUploadFile(PreUploadFile): @@ -331,5 +333,5 @@ class PreUploadSettings(PreUploadConfig): # We validated configs in isolation, now do one final pass altogether. - self.source = '{%s}' % '|'.join(self.paths) + self.source = '{' + '|'.join(self.paths) + '}' self._validate() diff --git a/rh/config_test.py b/rh/config_test.py index 80fc832..df3afb6 100755 --- a/rh/config_test.py +++ b/rh/config_test.py @@ -27,25 +27,25 @@ REPO_ROOT = os.path.dirname(os.path.dirname(REPOTOOLS)) def assertEqual(msg, exp, actual): """Assert |exp| equals |actual|.""" - assert exp == actual, '%s: expected "%s" but got "%s"' % (msg, exp, actual) + assert exp == actual, f'{msg}: expected "{exp}" but got "{actual}"' def assertEnv(var, value): """Assert |var| is set in the environment as |value|.""" - assert var in os.environ, '$%s missing in environment' % (var,) - assertEqual('env[%s]' % (var,), value, os.environ[var]) + assert var in os.environ, f'${var} missing in environment' + assertEqual(f'env[{var}]', value, os.environ[var]) def check_commit_id(commit): """Check |commit| looks like a git commit id.""" - assert len(commit) == 40, 'commit "%s" must be 40 chars' % (commit,) + assert len(commit) == 40, f'commit "{commit}" must be 40 chars' assert re.match(r'^[a-f0-9]+$', commit), \ - 'commit "%s" must be all hex' % (commit,) + f'commit "{commit}" must be all hex' def check_commit_msg(msg): """Check the ${PREUPLOAD_COMMIT_MESSAGE} setting.""" - assert len(msg) > 1, 'commit message must be at least 2 bytes: %s' + assert len(msg) > 1, f'commit message must be at least 2 bytes: {msg}' def check_repo_root(root): @@ -100,7 +100,7 @@ def main(argv): check_repo_root(opts.repo_root) check_files(opts.files) except AssertionError as e: - print('error: %s' % (e,), file=sys.stderr) + print(f'error: {e}', file=sys.stderr) return 1 return 0 diff --git a/rh/config_unittest.py b/rh/config_unittest.py index 3e3e470..475dc22 100755 --- a/rh/config_unittest.py +++ b/rh/config_unittest.py @@ -57,7 +57,7 @@ class FileTestCase(unittest.TestCase): Path to the file where the configuration was written. """ path = os.path.join(self.tempdir, filename) - with open(path, 'w') as fp: + with open(path, 'w', encoding='utf-8') as fp: fp.write(data) return path @@ -35,7 +35,7 @@ def get_upstream_remote(): branch = result.stdout.strip() # Then get the remote associated with this branch. - cmd = ['git', 'config', 'branch.%s.remote' % branch] + cmd = ['git', 'config', f'branch.{branch}.remote'] result = rh.utils.run(cmd, capture_output=True) return result.stdout.strip() @@ -52,14 +52,14 @@ def get_upstream_branch(): if not current_branch: raise ValueError('Need to be on a tracking branch') - cfg_option = 'branch.' + current_branch + '.%s' - cmd = ['git', 'config', cfg_option % 'merge'] + cfg_option = 'branch.' + current_branch + '.' + cmd = ['git', 'config', cfg_option + 'merge'] result = rh.utils.run(cmd, capture_output=True) full_upstream = result.stdout.strip() # If remote is not fully qualified, add an implicit namespace. if '/' not in full_upstream: - full_upstream = 'refs/heads/%s' % full_upstream - cmd = ['git', 'config', cfg_option % 'remote'] + full_upstream = f'refs/heads/{full_upstream}' + cmd = ['git', 'config', cfg_option + 'remote'] result = rh.utils.run(cmd, capture_output=True) remote = result.stdout.strip() if not remote or not full_upstream: @@ -77,7 +77,7 @@ def get_commit_for_ref(ref): def get_remote_revision(ref, remote): """Returns the remote revision for this ref.""" - prefix = 'refs/remotes/%s/' % remote + prefix = f'refs/remotes/{remote}/' if ref.startswith(prefix): return ref[len(prefix):] return ref @@ -99,7 +99,7 @@ def get_file_content(commit, path): a full file, you should check that first. One way to detect is that the content will not have any newlines. """ - cmd = ['git', 'show', '%s:%s' % (commit, path)] + cmd = ['git', 'show', f'{commit}:{path}'] return rh.utils.run(cmd, capture_output=True).stdout @@ -147,7 +147,7 @@ def raw_diff(path, target): for line in diff_lines: match = DIFF_RE.match(line) if not match: - raise ValueError('Failed to parse diff output: %s' % line) + raise ValueError(f'Failed to parse diff output: {line}') rawdiff = RawDiffEntry(**match.groupdict()) rawdiff.src_mode = int(rawdiff.src_mode) rawdiff.dst_mode = int(rawdiff.dst_mode) @@ -164,12 +164,12 @@ def get_affected_files(commit): Returns: A list of modified/added (and perhaps deleted) files """ - return raw_diff(os.getcwd(), '%s^-' % commit) + return raw_diff(os.getcwd(), f'{commit}^-') def get_commits(ignore_merged_commits=False): """Returns a list of commits for this review.""" - cmd = ['git', 'rev-list', '%s..' % get_upstream_branch()] + cmd = ['git', 'rev-list', f'{get_upstream_branch()}..'] if ignore_merged_commits: cmd.append('--first-parent') return rh.utils.run(cmd, capture_output=True).stdout.split() @@ -191,7 +191,7 @@ def find_repo_root(path=None): while not os.path.exists(os.path.join(path, '.repo')): path = os.path.dirname(path) if path == '/': - raise ValueError('Could not locate .repo in %s' % orig_path) + raise ValueError(f'Could not locate .repo in {orig_path}') return path diff --git a/rh/hooks.py b/rh/hooks.py index 0b3bb29..d473cb9 100644 --- a/rh/hooks.py +++ b/rh/hooks.py @@ -83,7 +83,7 @@ class Placeholders(object): else: # First scan for exact matches for key, val in replacements.items(): - var = '${%s}' % (key,) + var = '${' + key + '}' if arg == var: if isinstance(val, str): ret.append(val) @@ -98,7 +98,7 @@ class Placeholders(object): if isinstance(val, str): return val return ' '.join(val) - ret.append(re.sub(r'\$\{(%s)\}' % ('|'.join(all_vars),), + ret.append(re.sub(r'\$\{(' + '|'.join(all_vars) + r')\}', replace, arg)) return ret @@ -111,7 +111,7 @@ class Placeholders(object): def get(self, var): """Helper function to get the replacement |var| value.""" - return getattr(self, 'var_%s' % (var,)) + return getattr(self, f'var_{var}') @property def var_PREUPLOAD_COMMIT_MESSAGE(self): @@ -400,11 +400,11 @@ def check_google_java_format(project, commit, _desc, _diff, options=None): def check_commit_msg_bug_field(project, commit, desc, _diff, options=None): """Check the commit message for a 'Bug:' line.""" field = 'Bug' - regex = r'^%s: (None|[0-9]+(, [0-9]+)*)$' % (field,) + regex = fr'^{field}: (None|[0-9]+(, [0-9]+)*)$' check_re = re.compile(regex) if options.args(): - raise ValueError('commit msg %s check takes no options' % (field,)) + raise ValueError(f'commit msg {field} check takes no options') found = [] for line in desc.splitlines(): @@ -412,23 +412,25 @@ def check_commit_msg_bug_field(project, commit, desc, _diff, options=None): found.append(line) if not found: - error = ('Commit message is missing a "%s:" line. It must match the\n' - 'following case-sensitive regex:\n\n %s') % (field, regex) + error = ( + f'Commit message is missing a "{field}:" line. It must match the\n' + f'following case-sensitive regex:\n\n {regex}' + ) else: return None - return [rh.results.HookResult('commit msg: "%s:" check' % (field,), + return [rh.results.HookResult(f'commit msg: "{field}:" check', project, commit, error=error)] def check_commit_msg_changeid_field(project, commit, desc, _diff, options=None): """Check the commit message for a 'Change-Id:' line.""" field = 'Change-Id' - regex = r'^%s: I[a-f0-9]+$' % (field,) + regex = fr'^{field}: I[a-f0-9]+$' check_re = re.compile(regex) if options.args(): - raise ValueError('commit msg %s check takes no options' % (field,)) + raise ValueError(f'commit msg {field} check takes no options') found = [] for line in desc.splitlines(): @@ -436,15 +438,17 @@ def check_commit_msg_changeid_field(project, commit, desc, _diff, options=None): found.append(line) if not found: - error = ('Commit message is missing a "%s:" line. It must match the\n' - 'following case-sensitive regex:\n\n %s') % (field, regex) + error = ( + f'Commit message is missing a "{field}:" line. It must match the\n' + f'following case-sensitive regex:\n\n {regex}' + ) elif len(found) > 1: - error = ('Commit message has too many "%s:" lines. There can be only ' - 'one.') % (field,) + error = (f'Commit message has too many "{field}:" lines. There can be ' + 'only one.') else: return None - return [rh.results.HookResult('commit msg: "%s:" check' % (field,), + return [rh.results.HookResult(f'commit msg: "{field}:" check', project, commit, error=error)] @@ -537,11 +541,11 @@ high-quality Test: descriptions. def check_commit_msg_test_field(project, commit, desc, _diff, options=None): """Check the commit message for a 'Test:' line.""" field = 'Test' - regex = r'^%s: .*$' % (field,) + regex = fr'^{field}: .*$' check_re = re.compile(regex) if options.args(): - raise ValueError('commit msg %s check takes no options' % (field,)) + raise ValueError(f'commit msg {field} check takes no options') found = [] for line in desc.splitlines(): @@ -553,7 +557,7 @@ def check_commit_msg_test_field(project, commit, desc, _diff, options=None): else: return None - return [rh.results.HookResult('commit msg: "%s:" check' % (field,), + return [rh.results.HookResult(f'commit msg: "{field}:" check', project, commit, error=error)] @@ -613,22 +617,23 @@ def check_commit_msg_relnote_field_format(project, commit, desc, _diff, quotes are escaped with a backslash. """ field = 'Relnote' - regex_relnote = r'^%s:.*$' % (field,) + regex_relnote = fr'^{field}:.*$' check_re_relnote = re.compile(regex_relnote, re.IGNORECASE) if options.args(): - raise ValueError('commit msg %s check takes no options' % (field,)) + raise ValueError(f'commit msg {field} check takes no options') # Check 1: Check for possible misspellings of the `Relnote:` field. # Regex for misspelled fields. - possible_field_misspells = {'Relnotes', 'ReleaseNote', - 'Rel-note', 'Rel note', - 'rel-notes', 'releasenotes', - 'release-note', 'release-notes'} - regex_field_misspells = r'^(%s): .*$' % ( - '|'.join(possible_field_misspells), - ) + possible_field_misspells = { + 'Relnotes', 'ReleaseNote', + 'Rel-note', 'Rel note', + 'rel-notes', 'releasenotes', + 'release-note', 'release-notes', + } + re_possible_field_misspells = '|'.join(possible_field_misspells) + regex_field_misspells = fr'^({re_possible_field_misspells}): .*$' check_re_field_misspells = re.compile(regex_field_misspells, re.IGNORECASE) ret = [] @@ -636,9 +641,9 @@ def check_commit_msg_relnote_field_format(project, commit, desc, _diff, if check_re_field_misspells.match(line): error = RELNOTE_MISSPELL_MSG % (regex_relnote, ) ret.append( - rh.results.HookResult(('commit msg: "%s:" ' - 'tag spelling error') % (field,), - project, commit, error=error)) + rh.results.HookResult( + f'commit msg: "{field}:" tag spelling error', + project, commit, error=error)) # Check 2: Check that multiline Relnotes are quoted. @@ -661,10 +666,9 @@ def check_commit_msg_relnote_field_format(project, commit, desc, _diff, if (not check_re_other_fields.findall(next_line) and not check_re_empty_string.match(next_line)): ret.append( - rh.results.HookResult(('commit msg: "%s:" ' - 'tag missing quotes') % (field,), - project, commit, - error=RELNOTE_MISSING_QUOTES_MSG)) + rh.results.HookResult( + f'commit msg: "{field}:" tag missing quotes', + project, commit, error=RELNOTE_MISSING_QUOTES_MSG)) break # Check 3: Check that multiline Relnotes contain matching quotes. @@ -696,10 +700,9 @@ def check_commit_msg_relnote_field_format(project, commit, desc, _diff, break if first_quote_found != second_quote_found: ret.append( - rh.results.HookResult(('commit msg: "%s:" ' - 'tag missing closing quote') % (field,), - project, commit, - error=RELNOTE_MISSING_QUOTES_MSG)) + rh.results.HookResult( + f'commit msg: "{field}:" tag missing closing quote', + project, commit, error=RELNOTE_MISSING_QUOTES_MSG)) # Check 4: Check that non-starting or non-ending quotes are escaped with a # backslash. @@ -717,7 +720,7 @@ def check_commit_msg_relnote_field_format(project, commit, desc, _diff, if '"""' in cur_line: break if line_needs_checking: - stripped_line = re.sub('^%s:' % field, '', cur_line, + stripped_line = re.sub(fr'^{field}:', '', cur_line, flags=re.IGNORECASE).strip() for i, character in enumerate(stripped_line): if i == 0: @@ -739,11 +742,9 @@ def check_commit_msg_relnote_field_format(project, commit, desc, _diff, break if uses_invalid_quotes: - ret.append(rh.results.HookResult(('commit msg: "%s:" ' - 'tag using unescaped ' - 'quotes') % (field,), - project, commit, - error=RELNOTE_INVALID_QUOTES_MSG)) + ret.append(rh.results.HookResult( + f'commit msg: "{field}:" tag using unescaped quotes', + project, commit, error=RELNOTE_INVALID_QUOTES_MSG)) return ret @@ -773,11 +774,11 @@ def check_commit_msg_relnote_for_current_txt(project, commit, desc, diff, options=None): """Check changes to current.txt contain the 'Relnote:' stanza.""" field = 'Relnote' - regex = r'^%s: .+$' % (field,) + regex = fr'^{field}: .+$' check_re = re.compile(regex, re.IGNORECASE) if options.args(): - raise ValueError('commit msg %s check takes no options' % (field,)) + raise ValueError(f'commit msg {field} check takes no options') filtered = _filter_diff( diff, @@ -798,7 +799,7 @@ def check_commit_msg_relnote_for_current_txt(project, commit, desc, diff, else: return None - return [rh.results.HookResult('commit msg: "%s:" check' % (field,), + return [rh.results.HookResult(f'commit msg: "{field}:" check', project, commit, error=error)] @@ -907,7 +908,7 @@ def check_rustfmt(project, commit, _desc, diff, options=None): # TODO(b/164111102): rustfmt stable does not support --check on stdin. # If no error is reported, compare stdin with stdout. if data != result.stdout: - msg = ('To fix, please run: %s' % + msg = ('To fix, please run: ' + rh.shell.cmd_to_str(cmd + [d.file])) ret.append(rh.results.HookResult( 'rustfmt', project, commit, error=msg, @@ -948,7 +949,7 @@ def check_xmllint(project, commit, _desc, diff, options=None): 'xsl', # Extensible Stylesheet Language. )) - filtered = _filter_diff(diff, [r'\.(%s)$' % '|'.join(extensions)]) + filtered = _filter_diff(diff, [r'\.(' + '|'.join(extensions) + r')$']) if not filtered: return None diff --git a/rh/hooks_unittest.py b/rh/hooks_unittest.py index 8466319..03f45ca 100755 --- a/rh/hooks_unittest.py +++ b/rh/hooks_unittest.py @@ -48,7 +48,7 @@ class HooksDocsTests(unittest.TestCase): """Extract the |section| text out of the readme.""" ret = [] in_section = False - with open(self.readme) as fp: + with open(self.readme, encoding='utf-8') as fp: for line in fp: if not in_section: # Look for the section like "## [Tool Paths]". @@ -66,22 +66,22 @@ class HooksDocsTests(unittest.TestCase): """Verify builtin hooks are documented.""" data = self._grab_section('[Builtin Hooks]') for hook in rh.hooks.BUILTIN_HOOKS: - self.assertIn('* `%s`:' % (hook,), data, - msg='README.md missing docs for hook "%s"' % (hook,)) + self.assertIn(f'* `{hook}`:', data, + msg=f'README.md missing docs for hook "{hook}"') def testToolPaths(self): """Verify tools are documented.""" data = self._grab_section('[Tool Paths]') for tool in rh.hooks.TOOL_PATHS: - self.assertIn('* `%s`:' % (tool,), data, - msg='README.md missing docs for tool "%s"' % (tool,)) + self.assertIn(f'* `{tool}`:', data, + msg=f'README.md missing docs for tool "{tool}"') def testPlaceholders(self): """Verify placeholder replacement vars are documented.""" data = self._grab_section('Placeholders') for var in rh.hooks.Placeholders.vars(): - self.assertIn('* `${%s}`:' % (var,), data, - msg='README.md missing docs for var "%s"' % (var,)) + self.assertIn('* `${' + var + '}`:', data, + msg=f'README.md missing docs for var "{var}"') class PlaceholderTests(unittest.TestCase): @@ -149,8 +149,8 @@ class PlaceholderTests(unittest.TestCase): def testTheTester(self): """Make sure we have a test for every variable.""" for var in self.replacer.vars(): - self.assertIn('test%s' % (var,), dir(self), - msg='Missing unittest for variable %s' % (var,)) + self.assertIn(f'test{var}', dir(self), + msg=f'Missing unittest for variable {var}') def testPREUPLOAD_COMMIT_MESSAGE(self): """Verify handling of PREUPLOAD_COMMIT_MESSAGE.""" @@ -212,7 +212,7 @@ class HookOptionsTests(unittest.TestCase): # At least one replacement. Most real testing is in PlaceholderTests. args = ['who', 'goes', 'there ?', '${BUILD_OS} is great'] - exp_args = ['who', 'goes', 'there ?', '%s is great' % (m.return_value,)] + exp_args = ['who', 'goes', 'there ?', f'{m.return_value} is great'] self.assertEqual(exp_args, rh.hooks.HookOptions.expand_vars(args)) def testArgs(self): @@ -296,10 +296,10 @@ class BuiltinHooksTests(unittest.TestCase): ret = func(self.project, 'commit', desc, diff, options=self.options) if accept: self.assertFalse( - bool(ret), msg='Should have accepted: {{{%s}}}' % (desc,)) + bool(ret), msg='Should have accepted: {{{' + desc + '}}}') else: self.assertTrue( - bool(ret), msg='Should have rejected: {{{%s}}}' % (desc,)) + bool(ret), msg='Should have rejected: {{{' + desc + '}}}') def _test_file_filter(self, mock_check, func, files): """Helper for testing hooks that filter by files and run external tools. @@ -322,8 +322,8 @@ class BuiltinHooksTests(unittest.TestCase): def testTheTester(self, _mock_check, _mock_run): """Make sure we have a test for every hook.""" for hook in rh.hooks.BUILTIN_HOOKS: - self.assertIn('test_%s' % (hook,), dir(self), - msg='Missing unittest for builtin hook %s' % (hook,)) + self.assertIn(f'test_{hook}', dir(self), + msg=f'Missing unittest for builtin hook {hook}') def test_bpfmt(self, mock_check, _mock_run): """Verify the bpfmt builtin hook.""" diff --git a/rh/results.py b/rh/results.py index bdf7626..a7a4b49 100644 --- a/rh/results.py +++ b/rh/results.py @@ -51,11 +51,6 @@ class HookResult(object): def __bool__(self): return bool(self.error) - # pylint: disable=nonzero-method - def __nonzero__(self): - """Python 2/3 glue.""" - return self.__bool__() - def is_warning(self): return False diff --git a/rh/shell.py b/rh/shell.py index dda3be3..0321e44 100644 --- a/rh/shell.py +++ b/rh/shell.py @@ -73,18 +73,18 @@ def shell_quote(s): if c in _SHELL_QUOTABLE_CHARS: break else: - return s if s else u"''" + return s if s else "''" # See if we can use single quotes first. Output is nicer. if "'" not in s: - return u"'%s'" % s + return f"'{s}'" # Have to use double quotes. Escape the few chars that still expand when # used inside of double quotes. for c in _SHELL_ESCAPE_CHARS: if c in s: - s = s.replace(c, r'\%s' % c) - return u'"%s"' % s + s = s.replace(c, fr'\{c}') + return f'"{s}"' def shell_unquote(s): @@ -157,4 +157,4 @@ def boolean_shell_value(sval, default): if s in ('no', 'n', '0', 'false'): return False - raise ValueError('Could not decode as a boolean value: %r' % (sval,)) + raise ValueError(f'Could not decode as a boolean value: {sval!r}') diff --git a/rh/shell_unittest.py b/rh/shell_unittest.py index 21478cf..56b2321 100755 --- a/rh/shell_unittest.py +++ b/rh/shell_unittest.py @@ -40,8 +40,8 @@ class DiffTestCase(unittest.TestCase): def _assertEqual(self, func, test_input, test_output, result): """Like assertEqual but with built in diff support.""" diff = '\n'.join(list(self.differ.compare([test_output], [result]))) - msg = ('Expected %s to translate %r to %r, but got %r\n%s' % - (func, test_input, test_output, result, diff)) + msg = (f'Expected {func} to translate {test_input!r} to ' + f'{test_output!r}, but got {result!r}\n{diff}') self.assertEqual(test_output, result, msg) def _testData(self, functor, tests, check_type=True): @@ -65,8 +65,8 @@ class ShellQuoteTest(DiffTestCase): # Dict of expected output strings to input lists. tests_quote = { "''": '', - 'a': u'a', - "'a b c'": u'a b c', + 'a': 'a', + "'a b c'": 'a b c', "'a\tb'": 'a\tb', "'/a$file'": '/a$file', "'/a#file'": '/a#file', @@ -105,7 +105,7 @@ class CmdToStrTest(DiffTestCase): r"'a b' c": ['a b', 'c'], r'''a "b'c"''': ['a', "b'c"], r'''a "/'\$b" 'a b c' "xy'z"''': - [u'a', "/'$b", 'a b c', "xy'z"], + ['a', "/'$b", 'a b c', "xy'z"], '': [], } self._testData(rh.shell.cmd_to_str, tests) diff --git a/rh/terminal.py b/rh/terminal.py index 39c96ac..f69914c 100644 --- a/rh/terminal.py +++ b/rh/terminal.py @@ -136,18 +136,6 @@ def print_status_line(line, print_newline=False): sys.stderr.flush() -def get_input(prompt): - """Python 2/3 glue for raw_input/input differences.""" - try: - # pylint: disable=raw_input-builtin - return raw_input(prompt) - except NameError: - # Python 3 renamed raw_input() to input(), which is safe to call since - # it does not evaluate the input. - # pylint: disable=bad-builtin,input-builtin - return input(prompt) - - def boolean_prompt(prompt='Do you want to continue?', default=True, true_value='yes', false_value='no', prolog=None): """Helper function for processing boolean choice prompts. @@ -165,22 +153,22 @@ def boolean_prompt(prompt='Do you want to continue?', default=True, true_value, false_value = true_value.lower(), false_value.lower() true_text, false_text = true_value, false_value if true_value == false_value: - raise ValueError('true_value and false_value must differ: got %r' - % true_value) + raise ValueError( + f'true_value and false_value must differ: got {true_value!r}') if default: true_text = true_text[0].upper() + true_text[1:] else: false_text = false_text[0].upper() + false_text[1:] - prompt = ('\n%s (%s/%s)? ' % (prompt, true_text, false_text)) + prompt = f'\n{prompt} ({true_text}/{false_text})? ' if prolog: - prompt = ('\n%s\n%s' % (prolog, prompt)) + prompt = f'\n{prolog}\n{prompt}' while True: try: - response = get_input(prompt).lower() + response = input(prompt).lower() # pylint: disable=bad-builtin except EOFError: # If the user hits CTRL+D, or stdin is disabled, use the default. print() diff --git a/rh/utils.py b/rh/utils.py index aeab52f..14553a8 100644 --- a/rh/utils.py +++ b/rh/utils.py @@ -42,32 +42,23 @@ def timedelta_str(delta): total = delta.total_seconds() hours, rem = divmod(total, 3600) mins, secs = divmod(rem, 60) - ret = '%i.%03is' % (secs, delta.microseconds // 1000) + ret = f'{int(secs)}.{delta.microseconds // 1000:03}s' if mins: - ret = '%im%s' % (mins, ret) + ret = f'{int(mins)}m{ret}' if hours: - ret = '%ih%s' % (hours, ret) + ret = f'{int(hours)}h{ret}' return ret -class CompletedProcess(getattr(subprocess, 'CompletedProcess', object)): +class CompletedProcess(subprocess.CompletedProcess): """An object to store various attributes of a child process. This is akin to subprocess.CompletedProcess. """ - # The linter is confused by the getattr usage above. - # TODO(vapier): Drop this once we're Python 3-only and we drop getattr. - # pylint: disable=bad-option-value,super-on-old-class def __init__(self, args=None, returncode=None, stdout=None, stderr=None): - if sys.version_info.major < 3: - self.args = args - self.stdout = stdout - self.stderr = stderr - self.returncode = returncode - else: - super().__init__( - args=args, returncode=returncode, stdout=stdout, stderr=stderr) + super().__init__( + args=args, returncode=returncode, stdout=stdout, stderr=stderr) @property def cmd(self): @@ -96,8 +87,8 @@ class CalledProcessError(subprocess.CalledProcessError): def __init__(self, returncode, cmd, stdout=None, stderr=None, msg=None, exception=None): if exception is not None and not isinstance(exception, Exception): - raise TypeError('exception must be an exception instance; got %r' - % (exception,)) + raise TypeError( + f'exception must be an exception instance; got {exception!r}') super().__init__(returncode, cmd, stdout) # The parent class will set |output|, so delete it. @@ -126,7 +117,7 @@ class CalledProcessError(subprocess.CalledProcessError): A summary string for this result. """ items = [ - 'return code: %s; command: %s' % (self.returncode, self.cmdstr), + f'return code: {self.returncode}; command: {self.cmdstr}', ] if stderr and self.stderr: items.append(self.stderr) @@ -180,7 +171,7 @@ def _kill_child_process(proc, int_timeout, kill_timeout, cmd, original_handler, # Still doesn't want to die. Too bad, so sad, time to die. proc.kill() except EnvironmentError as e: - print('Ignoring unhandled exception in _kill_child_process: %s' % e, + print(f'Ignoring unhandled exception in _kill_child_process: {e}', file=sys.stderr) # Ensure our child process has been reaped, but don't wait forever. @@ -189,7 +180,7 @@ def _kill_child_process(proc, int_timeout, kill_timeout, cmd, original_handler, if not rh.signals.relay_signal(original_handler, signum, frame): # Mock up our own, matching exit code for signaling. raise TerminateCalledProcessError( - signum << 8, cmd, msg='Received signal %i' % signum) + signum << 8, cmd, msg=f'Received signal {signum}') class _Popen(subprocess.Popen): @@ -205,7 +196,7 @@ class _Popen(subprocess.Popen): process has knowingly been waitpid'd already. """ - # pylint: disable=arguments-differ + # pylint: disable=arguments-differ,arguments-renamed def send_signal(self, signum): if self.returncode is not None: # The original implementation in Popen allows signaling whatever @@ -254,7 +245,7 @@ class _Popen(subprocess.Popen): # We use the keyword arg |input| which trips up pylint checks. -# pylint: disable=redefined-builtin,input-builtin +# pylint: disable=redefined-builtin def run(cmd, redirect_stdout=False, redirect_stderr=False, cwd=None, input=None, shell=False, env=None, extra_env=None, combine_stdout_stderr=False, check=True, int_timeout=1, kill_timeout=1, capture_output=False, @@ -408,9 +399,9 @@ def run(cmd, redirect_stdout=False, redirect_stderr=False, cwd=None, input=None, result.returncode = proc.returncode if check and proc.returncode: - msg = 'cwd=%s' % cwd + msg = f'cwd={cwd}' if extra_env: - msg += ', extra env=%s' % extra_env + msg += f', extra env={extra_env}' raise CalledProcessError( result.returncode, result.cmd, msg=msg, stdout=ensure_text(result.stdout), @@ -445,4 +436,4 @@ def run(cmd, redirect_stdout=False, redirect_stderr=False, cwd=None, input=None, result.stderr = ensure_text(result.stderr) return result -# pylint: enable=redefined-builtin,input-builtin +# pylint: enable=redefined-builtin diff --git a/rh/utils_unittest.py b/rh/utils_unittest.py index ea2ddaa..586dfcc 100755 --- a/rh/utils_unittest.py +++ b/rh/utils_unittest.py @@ -152,13 +152,13 @@ class RunCommandTests(unittest.TestCase): def test_stdout_utf8(self): """Verify reading UTF-8 data works.""" ret = rh.utils.run(['printf', r'\xc3\x9f'], redirect_stdout=True) - self.assertEqual(u'ß', ret.stdout) + self.assertEqual('ß', ret.stdout) self.assertIsNone(ret.stderr) def test_stdin_utf8(self): """Verify writing UTF-8 data works.""" - ret = rh.utils.run(['cat'], redirect_stdout=True, input=u'ß') - self.assertEqual(u'ß', ret.stdout) + ret = rh.utils.run(['cat'], redirect_stdout=True, input='ß') + self.assertEqual('ß', ret.stdout) self.assertIsNone(ret.stderr) def test_check_false(self): diff --git a/tools/android_test_mapping_format.py b/tools/android_test_mapping_format.py index ae784cf..a71b94b 100755 --- a/tools/android_test_mapping_format.py +++ b/tools/android_test_mapping_format.py @@ -184,12 +184,13 @@ def main(argv): if opts.commit: json_data = rh.git.get_file_content(opts.commit, filename) else: - with open(os.path.join(opts.project_dir, filename)) as file: + with open(os.path.join(opts.project_dir, filename), + encoding='utf-8') as file: json_data = file.read() process_file(json_data) except: - print('Visit %s for details about the format of TEST_MAPPING ' - 'file.' % _TEST_MAPPING_URL, file=sys.stderr) + print('Visit {_TEST_MAPPING_URL} for details about the format of ' + 'TEST_MAPPING file.', file=sys.stderr) raise diff --git a/tools/android_test_mapping_format_unittest.py b/tools/android_test_mapping_format_unittest.py index 14bae32..cf3c3ca 100755 --- a/tools/android_test_mapping_format_unittest.py +++ b/tools/android_test_mapping_format_unittest.py @@ -198,25 +198,25 @@ class AndroidTestMappingFormatTests(unittest.TestCase): def test_valid_test_mapping(self): """Verify that the check doesn't raise any error for valid test mapping. """ - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_VALID_TEST_MAPPING) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: android_test_mapping_format.process_file(file.read()) def test_invalid_test_mapping_bad_json(self): """Verify that TEST_MAPPING file with bad json can be detected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_JSON) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( ValueError, android_test_mapping_format.process_file, file.read()) def test_invalid_test_mapping_wrong_test_key(self): """Verify that test config using wrong key can be detected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_TEST_WRONG_KEY) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( android_test_mapping_format.InvalidTestMappingError, android_test_mapping_format.process_file, @@ -224,9 +224,9 @@ class AndroidTestMappingFormatTests(unittest.TestCase): def test_invalid_test_mapping_wrong_test_value(self): """Verify that test config using wrong host value can be detected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_TEST_WRONG_HOST_VALUE) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( android_test_mapping_format.InvalidTestMappingError, android_test_mapping_format.process_file, @@ -234,16 +234,16 @@ class AndroidTestMappingFormatTests(unittest.TestCase): def test_invalid_test_mapping_wrong_preferred_targets_value(self): """Verify invalid preferred_targets are rejected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_TEST_WRONG_PREFERRED_TARGETS_VALUE_NONE_LIST) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( android_test_mapping_format.InvalidTestMappingError, android_test_mapping_format.process_file, file.read()) - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_TEST_WRONG_PREFERRED_TARGETS_VALUE_WRONG_TYPE) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( android_test_mapping_format.InvalidTestMappingError, android_test_mapping_format.process_file, @@ -251,9 +251,9 @@ class AndroidTestMappingFormatTests(unittest.TestCase): def test_invalid_test_mapping_wrong_test_option(self): """Verify that test config using wrong option can be detected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_TEST_WRONG_OPTION) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( android_test_mapping_format.InvalidTestMappingError, android_test_mapping_format.process_file, @@ -261,9 +261,9 @@ class AndroidTestMappingFormatTests(unittest.TestCase): def test_invalid_test_mapping_wrong_import_key(self): """Verify that import setting using wrong key can be detected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_IMPORT_WRONG_KEY) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( android_test_mapping_format.InvalidTestMappingError, android_test_mapping_format.process_file, @@ -271,9 +271,9 @@ class AndroidTestMappingFormatTests(unittest.TestCase): def test_invalid_test_mapping_wrong_import_value(self): """Verify that import setting using wrong value can be detected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_IMPORT_WRONG_IMPORT_VALUE) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( android_test_mapping_format.InvalidTestMappingError, android_test_mapping_format.process_file, @@ -281,9 +281,9 @@ class AndroidTestMappingFormatTests(unittest.TestCase): def test_invalid_test_mapping_file_patterns_value(self): """Verify that file_patterns using wrong value can be detected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_BAD_FILE_PATTERNS) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( android_test_mapping_format.InvalidTestMappingError, android_test_mapping_format.process_file, @@ -291,16 +291,16 @@ class AndroidTestMappingFormatTests(unittest.TestCase): def test_valid_test_mapping_file_with_supported_comments(self): """Verify that '//'-format comment can be filtered.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_TEST_MAPPING_WITH_SUPPORTED_COMMENTS) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: android_test_mapping_format.process_file(file.read()) def test_valid_test_mapping_file_with_non_supported_comments(self): """Verify that non-supported comment can be detected.""" - with open(self.test_mapping_file, 'w') as file: + with open(self.test_mapping_file, 'w', encoding='utf-8') as file: file.write(_TEST_MAPPING_WITH_NON_SUPPORTED_COMMENTS) - with open(self.test_mapping_file, 'r') as file: + with open(self.test_mapping_file, 'r', encoding='utf-8') as file: self.assertRaises( ValueError, android_test_mapping_format.process_file, file.read()) diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl index 2d42eb9..b01c36a 100755 --- a/tools/checkpatch.pl +++ b/tools/checkpatch.pl @@ -1,9 +1,11 @@ #!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# # (c) 2001, Dave Jones. (the file handling bit) # (c) 2005, Joel Schopp <jschopp@austin.ibm.com> (the ugly bit) # (c) 2007,2008, Andy Whitcroft <apw@uk.ibm.com> (new conditions, test suite) # (c) 2008-2010 Andy Whitcroft <apw@canonical.com> -# Licensed under the terms of the GNU GPL License version 2 +# (c) 2010-2018 Joe Perches <joe@perches.com> use strict; use warnings; @@ -11,6 +13,7 @@ use POSIX; use File::Basename; use Cwd 'abs_path'; use Term::ANSIColor qw(:constants); +use Encode qw(decode encode); my $P = $0; my $D = dirname(abs_path($P)); @@ -20,6 +23,9 @@ my $V = '0.32'; use Getopt::Long qw(:config no_auto_abbrev); my $quiet = 0; +my $verbose = 0; +my %verbose_messages = (); +my %verbose_emitted = (); my $tree = 1; my $chk_signoff = 1; my $chk_patch = 1; @@ -40,6 +46,8 @@ my $list_types = 0; my $fix = 0; my $fix_inplace = 0; my $root; +my $gitroot = $ENV{'GIT_DIR'}; +$gitroot = ".git" if !defined($gitroot); my %debug; my %camelcase = (); my %use_type = (); @@ -48,17 +56,23 @@ my %ignore_type = (); my @ignore = (); my $help = 0; my $configuration_file = ".checkpatch.conf"; -my $max_line_length = 80; +my $max_line_length = 100; my $ignore_perl_version = 0; my $minimum_perl_version = 5.10.0; my $min_conf_desc_length = 4; my $spelling_file = "$D/spelling.txt"; my $codespell = 0; my $codespellfile = "/usr/share/codespell/dictionary.txt"; +my $user_codespellfile = ""; my $conststructsfile = "$D/const_structs.checkpatch"; -my $typedefsfile = ""; +my $docsfile = "$D/../Documentation/dev-tools/checkpatch.rst"; +my $typedefsfile; my $color = "auto"; -my $allow_c99_comments = 1; +my $allow_c99_comments = 1; # Can be overridden by --ignore C99_COMMENT_TOLERANCE +# git output parsing needs US English output, so first set backtick child process LANGUAGE +my $git_command ='export LANGUAGE=en_US.UTF-8; git'; +my $tabsize = 8; +my ${CONFIG_} = "CONFIG_"; sub help { my ($exitcode) = @_; @@ -69,6 +83,7 @@ Version: $V Options: -q, --quiet quiet + -v, --verbose verbose mode --no-tree run without a kernel tree --no-signoff do not check for 'Signed-off-by' line --patch treat FILE as patchfile (default) @@ -91,8 +106,11 @@ Options: --types TYPE(,TYPE2...) show only these comma separated message types --ignore TYPE(,TYPE2...) ignore various comma separated message types --show-types show the specific message type in the output - --max-line-length=n set the maximum line length, if exceeded, warn + --max-line-length=n set the maximum line length, (default $max_line_length) + if exceeded, warn on patches + requires --strict for use with --file --min-conf-desc-length=n set the min description length, if shorter, warn + --tab-size=n set the number of spaces for tab (default $tabsize) --root=PATH PATH to the kernel tree root --no-summary suppress the per-file summary --mailback only produce a report in case of warnings/errors @@ -113,11 +131,13 @@ Options: --ignore-perl-version override checking of perl version. expect runtime errors. --codespell Use the codespell dictionary for spelling/typos - (default:/usr/share/codespell/dictionary.txt) + (default:$codespellfile) --codespellfile Use this codespell dictionary --typedefsfile Read additional types from this file --color[=WHEN] Use colors 'always', 'never', or only when output is a terminal ('auto'). Default is 'auto'. + --kconfig-prefix=WORD use WORD as a prefix for Kconfig symbols (default + ${CONFIG_}) -h, --help, --version display this help and exit When FILE is - read standard input. @@ -144,15 +164,51 @@ sub list_types { my $text = <$script>; close($script); - my @types = (); + my %types = (); # Also catch when type or level is passed through a variable - for ($text =~ /(?:(?:\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { - push (@types, $_); + while ($text =~ /(?:(\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { + if (defined($1)) { + if (exists($types{$2})) { + $types{$2} .= ",$1" if ($types{$2} ne $1); + } else { + $types{$2} = $1; + } + } else { + $types{$2} = "UNDETERMINED"; + } } - @types = sort(uniq(@types)); + print("#\tMessage type\n\n"); - foreach my $type (@types) { + if ($color) { + print(" ( Color coding: "); + print(RED . "ERROR" . RESET); + print(" | "); + print(YELLOW . "WARNING" . RESET); + print(" | "); + print(GREEN . "CHECK" . RESET); + print(" | "); + print("Multiple levels / Undetermined"); + print(" )\n\n"); + } + + foreach my $type (sort keys %types) { + my $orig_type = $type; + if ($color) { + my $level = $types{$type}; + if ($level eq "ERROR") { + $type = RED . $type . RESET; + } elsif ($level eq "WARN") { + $type = YELLOW . $type . RESET; + } elsif ($level eq "CHK") { + $type = GREEN . $type . RESET; + } + } print(++$count . "\t" . $type . "\n"); + if ($verbose && exists($verbose_messages{$orig_type})) { + my $message = $verbose_messages{$orig_type}; + $message =~ s/\n/\n\t/g; + print("\t" . $message . "\n\n"); + } } exit($exitcode); @@ -184,6 +240,46 @@ if (-f $conf) { unshift(@ARGV, @conf_args) if @conf_args; } +sub load_docs { + open(my $docs, '<', "$docsfile") + or warn "$P: Can't read the documentation file $docsfile $!\n"; + + my $type = ''; + my $desc = ''; + my $in_desc = 0; + + while (<$docs>) { + chomp; + my $line = $_; + $line =~ s/\s+$//; + + if ($line =~ /^\s*\*\*(.+)\*\*$/) { + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + $type = $1; + $desc = ''; + $in_desc = 1; + } elsif ($in_desc) { + if ($line =~ /^(?:\s{4,}|$)/) { + $line =~ s/^\s{4}//; + $desc .= $line; + $desc .= "\n"; + } else { + $verbose_messages{$type} = trim($desc); + $type = ''; + $desc = ''; + $in_desc = 0; + } + } + } + + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + close($docs); +} + # Perl's Getopt::Long allows options to take optional arguments after a space. # Prevent --color by itself from consuming other arguments foreach (@ARGV) { @@ -194,6 +290,7 @@ foreach (@ARGV) { GetOptions( 'q|quiet+' => \$quiet, + 'v|verbose!' => \$verbose, 'tree!' => \$tree, 'signoff!' => \$chk_signoff, 'patch!' => \$chk_patch, @@ -210,6 +307,7 @@ GetOptions( 'list-types!' => \$list_types, 'max-line-length=i' => \$max_line_length, 'min-conf-desc-length=i' => \$min_conf_desc_length, + 'tab-size=i' => \$tabsize, 'root=s' => \$root, 'summary!' => \$summary, 'mailback!' => \$mailback, @@ -220,17 +318,57 @@ GetOptions( 'debug=s' => \%debug, 'test-only=s' => \$tst_only, 'codespell!' => \$codespell, - 'codespellfile=s' => \$codespellfile, + 'codespellfile=s' => \$user_codespellfile, 'typedefsfile=s' => \$typedefsfile, 'color=s' => \$color, 'no-color' => \$color, #keep old behaviors of -nocolor 'nocolor' => \$color, #keep old behaviors of -nocolor + 'kconfig-prefix=s' => \${CONFIG_}, 'h|help' => \$help, 'version' => \$help -) or help(1); +) or $help = 2; + +if ($user_codespellfile) { + # Use the user provided codespell file unconditionally + $codespellfile = $user_codespellfile; +} elsif (!(-f $codespellfile)) { + # If /usr/share/codespell/dictionary.txt is not present, try to find it + # under codespell's install directory: <codespell_root>/data/dictionary.txt + if (($codespell || $help) && which("codespell") ne "" && which("python") ne "") { + my $python_codespell_dict = << "EOF"; + +import os.path as op +import codespell_lib +codespell_dir = op.dirname(codespell_lib.__file__) +codespell_file = op.join(codespell_dir, 'data', 'dictionary.txt') +print(codespell_file, end='') +EOF + + my $codespell_dict = `python -c "$python_codespell_dict" 2> /dev/null`; + $codespellfile = $codespell_dict if (-f $codespell_dict); + } +} -help(0) if ($help); +# $help is 1 if either -h, --help or --version is passed as option - exitcode: 0 +# $help is 2 if invalid option is passed - exitcode: 1 +help($help - 1) if ($help); + +die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix)); +die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse); + +if ($color =~ /^[01]$/) { + $color = !$color; +} elsif ($color =~ /^always$/i) { + $color = 1; +} elsif ($color =~ /^never$/i) { + $color = 0; +} elsif ($color =~ /^auto$/i) { + $color = (-t STDOUT); +} else { + die "$P: Invalid color mode: $color\n"; +} +load_docs() if ($verbose); list_types(0) if ($list_types); $fix = 1 if ($fix_inplace); @@ -238,11 +376,11 @@ $check_orig = $check; my $exit = 0; +my $perl_version_ok = 1; if ($^V && $^V lt $minimum_perl_version) { + $perl_version_ok = 0; printf "$P: requires at least perl version %vd\n", $minimum_perl_version; - if (!$ignore_perl_version) { - exit(1); - } + exit(1) if (!$ignore_perl_version); } #if no filenames are given, push '-' to read patch from stdin @@ -250,17 +388,8 @@ if ($#ARGV < 0) { push(@ARGV, '-'); } -if ($color =~ /^[01]$/) { - $color = !$color; -} elsif ($color =~ /^always$/i) { - $color = 1; -} elsif ($color =~ /^never$/i) { - $color = 0; -} elsif ($color =~ /^auto$/i) { - $color = (-t STDOUT); -} else { - die "Invalid color mode: $color\n"; -} +# skip TAB size 1 to avoid additional checks on $tabsize - 1 +die "$P: Invalid TAB size: $tabsize\n" if ($tabsize < 2); sub hash_save_array_words { my ($hashRef, $arrayRef) = @_; @@ -344,9 +473,10 @@ our $Sparse = qr{ __force| __iomem| __must_check| - __init_refok| __kprobes| __ref| + __refconst| + __refdata| __rcu| __private }x; @@ -360,6 +490,7 @@ our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeIni # We need \b after 'init' otherwise 'initconst' will cause a false positive in a check our $Attribute = qr{ const| + volatile| __percpu| __nocast| __safe| @@ -376,12 +507,14 @@ our $Attribute = qr{ __noclone| __deprecated| __read_mostly| + __ro_after_init| __kprobes| $InitAttribute| ____cacheline_aligned| ____cacheline_aligned_in_smp| ____cacheline_internodealigned_in_smp| - __weak + __weak| + __alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) }x; our $Modifier; our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; @@ -393,7 +526,7 @@ our $Binary = qr{(?i)0b[01]+$Int_type?}; our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; our $Int = qr{[0-9]+$Int_type?}; our $Octal = qr{0[0-7]+$Int_type?}; -our $String = qr{"[X\t]*"}; +our $String = qr{(?:\b[Lu])?"[X\t]*"}; our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; @@ -461,8 +594,19 @@ our $logFunctions = qr{(?x: seq_vprintf|seq_printf|seq_puts )}; +our $allocFunctions = qr{(?x: + (?:(?:devm_)? + (?:kv|k|v)[czm]alloc(?:_array)?(?:_node)? | + kstrdup(?:_const)? | + kmemdup(?:_nul)?) | + (?:\w+)?alloc_skb(?:_ip_align)? | + # dev_alloc_skb/netdev_alloc_skb, et al + dma_alloc_coherent +)}; + our $signature_tags = qr{(?xi: Signed-off-by:| + Co-developed-by:| Acked-by:| Tested-by:| Reviewed-by:| @@ -472,6 +616,88 @@ our $signature_tags = qr{(?xi: Cc: )}; +our $tracing_logging_tags = qr{(?xi: + [=-]*> | + <[=-]* | + \[ | + \] | + start | + called | + entered | + entry | + enter | + in | + inside | + here | + begin | + exit | + end | + done | + leave | + completed | + out | + return | + [\.\!:\s]* +)}; + +sub edit_distance_min { + my (@arr) = @_; + my $len = scalar @arr; + if ((scalar @arr) < 1) { + # if underflow, return + return; + } + my $min = $arr[0]; + for my $i (0 .. ($len-1)) { + if ($arr[$i] < $min) { + $min = $arr[$i]; + } + } + return $min; +} + +sub get_edit_distance { + my ($str1, $str2) = @_; + $str1 = lc($str1); + $str2 = lc($str2); + $str1 =~ s/-//g; + $str2 =~ s/-//g; + my $len1 = length($str1); + my $len2 = length($str2); + # two dimensional array storing minimum edit distance + my @distance; + for my $i (0 .. $len1) { + for my $j (0 .. $len2) { + if ($i == 0) { + $distance[$i][$j] = $j; + } elsif ($j == 0) { + $distance[$i][$j] = $i; + } elsif (substr($str1, $i-1, 1) eq substr($str2, $j-1, 1)) { + $distance[$i][$j] = $distance[$i - 1][$j - 1]; + } else { + my $dist1 = $distance[$i][$j - 1]; #insert distance + my $dist2 = $distance[$i - 1][$j]; # remove + my $dist3 = $distance[$i - 1][$j - 1]; #replace + $distance[$i][$j] = 1 + edit_distance_min($dist1, $dist2, $dist3); + } + } + } + return $distance[$len1][$len2]; +} + +sub find_standard_signature { + my ($sign_off) = @_; + my @standard_signature_tags = ( + 'Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Tested-by:', + 'Reviewed-by:', 'Reported-by:', 'Suggested-by:' + ); + foreach my $signature (@standard_signature_tags) { + return $signature if (get_edit_distance($sign_off, $signature) <= 2); + } + + return ""; +} + our @typeListMisordered = ( qr{char\s+(?:un)?signed}, qr{int\s+(?:(?:un)?signed\s+)?short\s}, @@ -560,6 +786,8 @@ our @mode_permission_funcs = ( ["__ATTR", 2], ); +my $word_pattern = '\b[A-Z]?[a-z]{2,}\b'; + #Create a search pattern for all these functions to speed up a loop below our $mode_perms_search = ""; foreach my $entry (@mode_permission_funcs) { @@ -568,6 +796,27 @@ foreach my $entry (@mode_permission_funcs) { } $mode_perms_search = "(?:${mode_perms_search})"; +our %deprecated_apis = ( + "synchronize_rcu_bh" => "synchronize_rcu", + "synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited", + "call_rcu_bh" => "call_rcu", + "rcu_barrier_bh" => "rcu_barrier", + "synchronize_sched" => "synchronize_rcu", + "synchronize_sched_expedited" => "synchronize_rcu_expedited", + "call_rcu_sched" => "call_rcu", + "rcu_barrier_sched" => "rcu_barrier", + "get_state_synchronize_sched" => "get_state_synchronize_rcu", + "cond_synchronize_sched" => "cond_synchronize_rcu", +); + +#Create a search pattern for all these strings to speed up a loop below +our $deprecated_apis_search = ""; +foreach my $entry (keys %deprecated_apis) { + $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); + $deprecated_apis_search .= $entry; +} +$deprecated_apis_search = "(?:${deprecated_apis_search})"; + our $mode_perms_world_writable = qr{ S_IWUGO | S_IWOTH | @@ -707,7 +956,7 @@ sub read_words { next; } - $$wordsRef .= '|' if ($$wordsRef ne ""); + $$wordsRef .= '|' if (defined $$wordsRef); $$wordsRef .= $line; } close($file); @@ -717,16 +966,18 @@ sub read_words { return 0; } -my $const_structs = ""; -read_words(\$const_structs, $conststructsfile) - or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; +my $const_structs; +if (show_type("CONST_STRUCT")) { + read_words(\$const_structs, $conststructsfile) + or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; +} -my $typeOtherTypedefs = ""; -if (length($typedefsfile)) { +if (defined($typedefsfile)) { + my $typeOtherTypedefs; read_words(\$typeOtherTypedefs, $typedefsfile) or warn "No additional types will be considered - file '$typedefsfile': $!\n"; + $typeTypedefs .= '|' . $typeOtherTypedefs if (defined $typeOtherTypedefs); } -$typeTypedefs .= '|' . $typeOtherTypedefs if ($typeOtherTypedefs ne ""); sub build_types { my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; @@ -765,12 +1016,12 @@ sub build_types { }x; $Type = qr{ $NonptrType - (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)? + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} (?:\s+$Inline|\s+$Modifier)* }x; $TypeMisordered = qr{ $NonptrTypeMisordered - (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+)? + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} (?:\s+$Inline|\s+$Modifier)* }x; $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; @@ -791,10 +1042,16 @@ our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; our $declaration_macros = qr{(?x: (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| - (?:$Storage\s+)?${Type}\s+uninitialized_var\s*\(| (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\( )}; +our %allow_repeated_words = ( + add => '', + added => '', + bad => '', + be => '', +); + sub deparenthesize { my ($string) = @_; return "" if (!defined($string)); @@ -835,14 +1092,29 @@ sub seed_camelcase_file { } } +our %maintained_status = (); + sub is_maintained_obsolete { my ($filename) = @_; return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); - my $status = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + if (!exists($maintained_status{$filename})) { + $maintained_status{$filename} = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + } + + return $maintained_status{$filename} =~ /obsolete/i; +} + +sub is_SPDX_License_valid { + my ($license) = @_; + + return 1 if (!$tree || which("python3") eq "" || !(-x "$root/scripts/spdxcheck.py") || !(-e "$gitroot")); - return $status =~ /obsolete/i; + my $root_path = abs_path($root); + my $status = `cd "$root_path"; echo "$license" | scripts/spdxcheck.py -`; + return 0 if ($status ne ""); + return 1; } my $camelcase_seeded = 0; @@ -855,8 +1127,8 @@ sub seed_camelcase_includes { $camelcase_seeded = 1; - if (-e ".git") { - my $git_last_include_commit = `git log --no-merges --pretty=format:"%h%n" -1 -- include`; + if (-e "$gitroot") { + my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; chomp $git_last_include_commit; $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; } else { @@ -883,8 +1155,8 @@ sub seed_camelcase_includes { return; } - if (-e ".git") { - $files = `git ls-files "include/*.h"`; + if (-e "$gitroot") { + $files = `${git_command} ls-files "include/*.h"`; @include_files = split('\n', $files); } @@ -903,18 +1175,28 @@ sub seed_camelcase_includes { } } +sub git_is_single_file { + my ($filename) = @_; + + return 0 if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} ls-files -- $filename 2>/dev/null`; + my $count = $output =~ tr/\n//; + return $count eq 1 && $output =~ m{^${filename}$}; +} + sub git_commit_info { my ($commit, $id, $desc) = @_; - return ($id, $desc) if ((which("git") eq "") || !(-e ".git")); + return ($id, $desc) if ((which("git") eq "") || !(-e "$gitroot")); - my $output = `git log --no-color --format='%H %s' -1 $commit 2>&1`; + my $output = `${git_command} log --no-color --format='%H %s' -1 $commit 2>&1`; $output =~ s/^\s*//gm; my @lines = split("\n", $output); return ($id, $desc) if ($#lines < 0); - if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous\./) { + if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous/) { # Maybe one day convert this block of bash into something that returns # all matching commit ids, but it's very slow... # @@ -924,7 +1206,8 @@ sub git_commit_info { # git log --format='%H %s' -1 $line | # echo "commit $(cut -c 1-12,41-)" # done - } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./) { + } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./ || + $lines[0] =~ /^fatal: bad object $commit/) { $id = undef; } else { $id = substr($lines[0], 0, 12); @@ -945,7 +1228,7 @@ my $fixlinenr = -1; # If input is git commits, extract all commits from the commit expressions. # For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. -die "$P: No git repository found\n" if ($git && !-e ".git"); +die "$P: No git repository found\n" if ($git && !-e "$gitroot"); if ($git) { my @commits = (); @@ -958,7 +1241,7 @@ if ($git) { } else { $git_range = "-1 $commit_expr"; } - my $lines = `git log --no-color --no-merges --pretty=format:'%H %s' $git_range`; + my $lines = `${git_command} log --no-color --no-merges --pretty=format:'%H %s' $git_range`; foreach my $line (split(/\n/, $lines)) { $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; next if (!defined($1) || !defined($2)); @@ -973,8 +1256,12 @@ if ($git) { } my $vname; +$allow_c99_comments = !defined $ignore_type{"C99_COMMENT_TOLERANCE"}; for my $filename (@ARGV) { my $FILE; + my $is_git_file = git_is_single_file($filename); + my $oldfile = $file; + $file = 1 if ($is_git_file); if ($git) { open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || die "$P: $filename: git format-patch failed - $!\n"; @@ -997,6 +1284,7 @@ for my $filename (@ARGV) { while (<$FILE>) { chomp; push(@rawlines, $_); + $vname = qq("$1") if ($filename eq '-' && $_ =~ m/^Subject:\s+(.+)/i); } close($FILE); @@ -1018,17 +1306,18 @@ for my $filename (@ARGV) { @modifierListFile = (); @typeListFile = (); build_types(); + $file = $oldfile if ($is_git_file); } if (!$quiet) { hash_show_words(\%use_type, "Used"); hash_show_words(\%ignore_type, "Ignored"); - if ($^V lt 5.10.0) { + if (!$perl_version_ok) { print << "EOM" NOTE: perl $^V is not modern enough to detect all possible issues. - An upgrade to at least perl v5.10.0 is suggested. + An upgrade to at least perl $minimum_perl_version is suggested. EOM } if ($exit) { @@ -1063,6 +1352,8 @@ sub parse_email { my ($formatted_email) = @_; my $name = ""; + my $quoted = ""; + my $name_comment = ""; my $address = ""; my $comment = ""; @@ -1093,42 +1384,76 @@ sub parse_email { } } - $name = trim($name); - $name =~ s/^\"|\"$//g; + # Extract comments from names excluding quoted parts + # "John D. (Doe)" - Do not extract + if ($name =~ s/\"(.+)\"//) { + $quoted = $1; + } + while ($name =~ s/\s*($balanced_parens)\s*/ /) { + $name_comment .= trim($1); + } + $name =~ s/^[ \"]+|[ \"]+$//g; + $name = trim("$quoted $name"); + $address = trim($address); $address =~ s/^\<|\>$//g; + $comment = trim($comment); if ($name =~ /[^\w \-]/i) { ##has "must quote" chars $name =~ s/(?<!\\)"/\\"/g; ##escape quotes $name = "\"$name\""; } - return ($name, $address, $comment); + return ($name, $name_comment, $address, $comment); } sub format_email { - my ($name, $address) = @_; + my ($name, $name_comment, $address, $comment) = @_; my $formatted_email; - $name = trim($name); - $name =~ s/^\"|\"$//g; + $name =~ s/^[ \"]+|[ \"]+$//g; $address = trim($address); + $address =~ s/(?:\.|\,|\")+$//; ##trailing commas, dots or quotes if ($name =~ /[^\w \-]/i) { ##has "must quote" chars $name =~ s/(?<!\\)"/\\"/g; ##escape quotes $name = "\"$name\""; } + $name_comment = trim($name_comment); + $name_comment = " $name_comment" if ($name_comment ne ""); + $comment = trim($comment); + $comment = " $comment" if ($comment ne ""); + if ("$name" eq "") { $formatted_email = "$address"; } else { - $formatted_email = "$name <$address>"; + $formatted_email = "$name$name_comment <$address>"; } - + $formatted_email .= "$comment"; return $formatted_email; } +sub reformat_email { + my ($email) = @_; + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + return format_email($email_name, $name_comment, $email_address, $comment); +} + +sub same_email_addresses { + my ($email1, $email2) = @_; + + my ($email1_name, $name1_comment, $email1_address, $comment1) = parse_email($email1); + my ($email2_name, $name2_comment, $email2_address, $comment2) = parse_email($email2); + + return $email1_name eq $email2_name && + $email1_address eq $email2_address && + $name1_comment eq $name2_comment && + $comment1 eq $comment2; +} + sub which { my ($bin) = @_; @@ -1162,7 +1487,7 @@ sub expand_tabs { if ($c eq "\t") { $res .= ' '; $n++; - for (; ($n % 8) != 0; $n++) { + for (; ($n % $tabsize) != 0; $n++) { $res .= ' '; } next; @@ -1591,8 +1916,16 @@ sub ctx_statement_level { sub ctx_locate_comment { my ($first_line, $end_line) = @_; + # If c99 comment on the current line, or the line before or after + my ($current_comment) = ($rawlines[$end_line - 1] =~ m@^\+.*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line - 2] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + # Catch a comment on the end of the line itself. - my ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); + ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); return $current_comment if (defined $current_comment); # Look through the context and try and figure out if there is a @@ -1986,7 +2319,16 @@ sub report { splice(@lines, 1, 1); $output = join("\n", @lines); } - $output = (split('\n', $output))[0] . "\n" if ($terse); + + if ($terse) { + $output = (split('\n', $output))[0] . "\n"; + } + + if ($verbose && exists($verbose_messages{$type}) && + !exists($verbose_emitted{$type})) { + $output .= $verbose_messages{$type} . "\n\n"; + $verbose_emitted{$type} = 1; + } push(our @report, $output); @@ -2175,7 +2517,7 @@ sub string_find_replace { sub tabify { my ($leading) = @_; - my $source_indent = 8; + my $source_indent = $tabsize; my $max_spaces_before_tab = $source_indent - 1; my $spaces_to_tab = " " x $source_indent; @@ -2217,6 +2559,28 @@ sub pos_last_openparen { return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; } +sub get_raw_comment { + my ($line, $rawline) = @_; + my $comment = ''; + + for my $i (0 .. (length($line) - 1)) { + if (substr($line, $i, 1) eq "$;") { + $comment .= substr($rawline, $i, 1); + } + } + + return $comment; +} + +sub exclude_global_initialisers { + my ($realfile) = @_; + + # Do not check for BPF programs (tools/testing/selftests/bpf/progs/*.c, samples/bpf/*_kern.c, *.bpf.c). + return $realfile =~ m@^tools/testing/selftests/bpf/progs/.*\.c$@ || + $realfile =~ m@^samples/bpf/.*_kern\.c$@ || + $realfile =~ m@/bpf/.*\.bpf\.c$@; +} + sub process { my $filename = shift; @@ -2233,16 +2597,24 @@ sub process { our $clean = 1; my $signoff = 0; + my $author = ''; + my $authorsignoff = 0; + my $author_sob = ''; my $is_patch = 0; + my $is_binding_patch = -1; my $in_header_lines = $file ? 0 : 1; my $in_commit_log = 0; #Scanning lines before patch + my $has_patch_separator = 0; #Found a --- line my $has_commit_log = 0; #Encountered lines before patch + my $commit_log_lines = 0; #Number of commit log lines my $commit_log_possible_stack_dump = 0; my $commit_log_long_line = 0; my $commit_log_has_diff = 0; my $reported_maintainer_file = 0; my $non_utf8_charset = 0; + my $last_git_commit_id_linenr = -1; + my $last_blank_line = 0; my $last_coalesced_string_linenr = -1; @@ -2293,7 +2665,7 @@ sub process { if ($rawline=~/^\+\+\+\s+(\S+)/) { $setup_docs = 0; - if ($1 =~ m@Documentation/admin-guide/kernel-parameters.rst$@) { + if ($1 =~ m@Documentation/admin-guide/kernel-parameters.txt$@) { $setup_docs = 1; } #next; @@ -2374,6 +2746,15 @@ sub process { $sline =~ s/$;/ /g; #with comments as spaces my $rawline = $rawlines[$linenr - 1]; + my $raw_comment = get_raw_comment($line, $rawline); + +# check if it's a mode change, rename or start of a patch + if (!$in_commit_log && + ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || + ($line =~ /^rename (?:from|to) \S+\s*$/ || + $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { + $is_patch = 1; + } #extract the line range in the file after the patch is applied if (!$in_commit_log && @@ -2475,6 +2856,19 @@ sub process { $check = $check_orig; } $checklicenseline = 1; + + if ($realfile !~ /^MAINTAINERS/) { + my $last_binding_patch = $is_binding_patch; + + $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; + + if (($last_binding_patch != -1) && + ($last_binding_patch ^ $is_binding_patch)) { + WARN("DT_SPLIT_BINDING_PATCH", + "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.rst\n"); + } + } + next; } @@ -2486,10 +2880,22 @@ sub process { $cnt_lines++ if ($realcnt != 0); +# Verify the existence of a commit log if appropriate +# 2 is used because a $signature is counted in $commit_log_lines + if ($in_commit_log) { + if ($line !~ /^\s*$/) { + $commit_log_lines++; #could be a $signature + } + } elsif ($has_commit_log && $commit_log_lines < 2) { + WARN("COMMIT_MESSAGE", + "Missing commit description - Add an appropriate one\n"); + $commit_log_lines = 2; #warn only once + } + # Check if the commit log has what seems like a diff which can confuse patch if ($in_commit_log && !$commit_log_has_diff && - (($line =~ m@^\s+diff\b.*a/[\w/]+@ && - $line =~ m@^\s+diff\b.*a/([\w/]+)\s+b/$1\b@) || + (($line =~ m@^\s+diff\b.*a/([\w/]+)@ && + $line =~ m@^\s+diff\b.*a/[\w/]+\s+b/$1\b@) || $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { ERROR("DIFF_IN_COMMIT_MSG", @@ -2507,10 +2913,61 @@ sub process { } } +# Check the patch for a From: + if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { + $author = $1; + my $curline = $linenr; + while(defined($rawlines[$curline]) && ($rawlines[$curline++] =~ /^[ \t]\s*(.*)/)) { + $author .= $1; + } + $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); + $author =~ s/"//g; + $author = reformat_email($author); + } + # Check the patch for a signoff: - if ($line =~ /^\s*signed-off-by:/i) { + if ($line =~ /^\s*signed-off-by:\s*(.*)/i) { $signoff++; $in_commit_log = 0; + if ($author ne '' && $authorsignoff != 1) { + if (same_email_addresses($1, $author)) { + $authorsignoff = 1; + } else { + my $ctx = $1; + my ($email_name, $email_comment, $email_address, $comment1) = parse_email($ctx); + my ($author_name, $author_comment, $author_address, $comment2) = parse_email($author); + + if (lc $email_address eq lc $author_address && $email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 2; + } elsif (lc $email_address eq lc $author_address) { + $author_sob = $ctx; + $authorsignoff = 3; + } elsif ($email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 4; + + my $address1 = $email_address; + my $address2 = $author_address; + + if ($address1 =~ /(\S+)\+\S+(\@.*)/) { + $address1 = "$1$2"; + } + if ($address2 =~ /(\S+)\+\S+(\@.*)/) { + $address2 = "$1$2"; + } + if ($address1 eq $address2) { + $authorsignoff = 5; + } + } + } + } + } + +# Check for patch separator + if ($line =~ /^---$/) { + $has_patch_separator = 1; + $in_commit_log = 0; } # Check if MAINTAINERS is being updated. If so, there's probably no need to @@ -2529,8 +2986,17 @@ sub process { my $ucfirst_sign_off = ucfirst(lc($sign_off)); if ($sign_off !~ /$signature_tags/) { - WARN("BAD_SIGN_OFF", - "Non-standard signature: $sign_off\n" . $herecurr); + my $suggested_signature = find_standard_signature($sign_off); + if ($suggested_signature eq "") { + WARN("BAD_SIGN_OFF", + "Non-standard signature: $sign_off\n" . $herecurr); + } else { + if (WARN("BAD_SIGN_OFF", + "Non-standard signature: '$sign_off' - perhaps '$suggested_signature'?\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/$sign_off/$suggested_signature/; + } + } } if (defined $space_before && $space_before ne "") { if (WARN("BAD_SIGN_OFF", @@ -2558,8 +3024,8 @@ sub process { } } - my ($email_name, $email_address, $comment) = parse_email($email); - my $suggested_email = format_email(($email_name, $email_address)); + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); if ($suggested_email eq "") { ERROR("BAD_SIGN_OFF", "Unrecognized email address: '$email'\n" . $herecurr); @@ -2569,11 +3035,77 @@ sub process { $dequoted =~ s/" </ </; # Don't force email to have quotes # Allow just an angle bracketed address - if ("$dequoted$comment" ne $email && - "<$email_address>$comment" ne $email && - "$suggested_email$comment" ne $email) { + if (!same_email_addresses($email, $suggested_email)) { + if (WARN("BAD_SIGN_OFF", + "email address '$email' might be better as '$suggested_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$suggested_email/; + } + } + + # Address part shouldn't have comments + my $stripped_address = $email_address; + $stripped_address =~ s/\([^\(\)]*\)//g; + if ($email_address ne $stripped_address) { + if (WARN("BAD_SIGN_OFF", + "address part of email should not have comments: '$email_address'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email_address\E/$stripped_address/; + } + } + + # Only one name comment should be allowed + my $comment_count = () = $name_comment =~ /\([^\)]+\)/g; + if ($comment_count > 1) { WARN("BAD_SIGN_OFF", - "email address '$email' might be better as '$suggested_email$comment'\n" . $herecurr); + "Use a single name comment in email: '$email'\n" . $herecurr); + } + + + # stable@vger.kernel.org or stable@kernel.org shouldn't + # have an email name. In addition comments should strictly + # begin with a # + if ($email =~ /^.*stable\@(?:vger\.)?kernel\.org/i) { + if (($comment ne "" && $comment !~ /^#.+/) || + ($email_name ne "")) { + my $cur_name = $email_name; + my $new_comment = $comment; + $cur_name =~ s/[a-zA-Z\s\-\"]+//g; + + # Remove brackets enclosing comment text + # and # from start of comments to get comment text + $new_comment =~ s/^\((.*)\)$/$1/; + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^[\s\#]+|\s+$//g; + + $new_comment = trim("$new_comment $cur_name") if ($cur_name ne $new_comment); + $new_comment = " # $new_comment" if ($new_comment ne ""); + my $new_email = "$email_address$new_comment"; + + if (WARN("BAD_STABLE_ADDRESS_STYLE", + "Invalid email format for stable: '$email', prefer '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } elsif ($comment ne "" && $comment !~ /^(?:#.+|\(.+\))$/) { + my $new_comment = $comment; + + # Extract comment text from within brackets or + # c89 style /*...*/ comments + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^\/\*(.*)\*\/$/$1/; + + $new_comment = trim($new_comment); + $new_comment =~ s/^[^\w]$//; # Single lettered comment with non word character is usually a typo + $new_comment = "($new_comment)" if ($new_comment ne ""); + my $new_email = format_email($email_name, $name_comment, $email_address, $new_comment); + + if (WARN("BAD_SIGN_OFF", + "Unexpected content after email: '$email', should be: '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } } } @@ -2587,6 +3119,24 @@ sub process { } else { $signatures{$sig_nospace} = 1; } + +# Check Co-developed-by: immediately followed by Signed-off-by: with same name and email + if ($sign_off =~ /^co-developed-by:$/i) { + if ($email eq $author) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . "$here\n" . $rawline); + } + if (!defined $lines[$linenr]) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline); + } elsif ($rawlines[$linenr] !~ /^\s*signed-off-by:\s*(.*)/i) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]); + } elsif ($1 ne $email) { + WARN("BAD_SIGN_OFF", + "Co-developed-by and Signed-off-by: name/email do not match \n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]); + } + } } # Check email subject for common tools that don't need to be mentioned @@ -2596,16 +3146,13 @@ sub process { "A patch subject line should describe the change not the tool that found it\n" . $herecurr); } -# Check for old stable address - if ($line =~ /^\s*cc:\s*.*<?\bstable\@kernel\.org\b>?.*$/i) { - ERROR("STABLE_ADDRESS", - "The 'stable' address should be 'stable\@vger.kernel.org'\n" . $herecurr); - } - -# Check for unwanted Gerrit info - if ($in_commit_log && $line =~ /^\s*change-id:/i) { - ERROR("GERRIT_CHANGE_ID", - "Remove Gerrit Change-Id's before submitting upstream.\n" . $herecurr); +# Check for Gerrit Change-Ids not in any patch context + if ($realfile eq '' && !$has_patch_separator && $line =~ /^\s*change-id:/i) { + if (ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } } # Check if the commit log is in a possible stack dump @@ -2613,8 +3160,10 @@ sub process { ($line =~ /^\s*(?:WARNING:|BUG:)/ || $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || # timestamp - $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/)) { - # stack dump address + $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) || + $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ || + $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at [0-9a-fA-F]+/) { + # stack dump address styles $commit_log_possible_stack_dump = 1; } @@ -2623,10 +3172,10 @@ sub process { length($line) > 75 && !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || # file delta changes - $line =~ /^\s*(?:[\w\.\-]+\/)++[\w\.\-]+:/ || + $line =~ /^\s*(?:[\w\.\-\+]*\/)++[\w\.\-\+]+:/ || # filename then : - $line =~ /^\s*(?:Fixes:|Link:)/i || - # A Fixes: or Link: line + $line =~ /^\s*(?:Fixes:|Link:|$signature_tags)/i || + # A Fixes: or Link: line or signature tag line $commit_log_possible_stack_dump)) { WARN("COMMIT_LOG_LONG_LINE", "Possible unwrapped commit description (prefer a maximum 75 chars per line)\n" . $herecurr); @@ -2639,11 +3188,30 @@ sub process { $commit_log_possible_stack_dump = 0; } +# Check for lines starting with a # + if ($in_commit_log && $line =~ /^#/) { + if (WARN("COMMIT_COMMENT_SYMBOL", + "Commit log lines starting with '#' are dropped by git as comments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^/ /; + } + } + # Check for git id commit length and improperly formed commit descriptions - if ($in_commit_log && !$commit_log_possible_stack_dump && - $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink):/i && +# A correctly formed commit description is: +# commit <SHA-1 hash length 12+ chars> ("Complete commit subject") +# with the commit subject '("' prefix and '")' suffix +# This is a fairly compilicated block as it tests for what appears to be +# bare SHA-1 hash with minimum length of 5. It also avoids several types of +# possible SHA-1 matches. +# A commit match can span multiple lines so this block attempts to find a +# complete typical commit on a maximum of 3 lines + if ($perl_version_ok && + $in_commit_log && !$commit_log_possible_stack_dump && + $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink|base-commit):/i && $line !~ /^This reverts commit [0-9a-f]{7,40}/ && - ($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + (($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + ($line =~ /\bcommit\s*$/i && defined($rawlines[$linenr]) && $rawlines[$linenr] =~ /^\s*[0-9a-f]{5,}\b/i)) || ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { @@ -2653,49 +3221,56 @@ sub process { my $long = 0; my $case = 1; my $space = 1; - my $hasdesc = 0; - my $hasparens = 0; my $id = '0123456789ab'; my $orig_desc = "commit description"; my $description = ""; + my $herectx = $herecurr; + my $has_parens = 0; + my $has_quotes = 0; + + my $input = $line; + if ($line =~ /(?:\bcommit\s+[0-9a-f]{5,}|\bcommit\s*$)/i) { + for (my $n = 0; $n < 2; $n++) { + if ($input =~ /\bcommit\s+[0-9a-f]{5,}\s*($balanced_parens)/i) { + $orig_desc = $1; + $has_parens = 1; + # Always strip leading/trailing parens then double quotes if existing + $orig_desc = substr($orig_desc, 1, -1); + if ($orig_desc =~ /^".*"$/) { + $orig_desc = substr($orig_desc, 1, -1); + $has_quotes = 1; + } + last; + } + last if ($#lines < $linenr + $n); + $input .= " " . trim($rawlines[$linenr + $n]); + $herectx .= "$rawlines[$linenr + $n]\n"; + } + $herectx = $herecurr if (!$has_parens); + } - if ($line =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { + if ($input =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { $init_char = $1; $orig_commit = lc($2); - } elsif ($line =~ /\b([0-9a-f]{12,40})\b/i) { + $short = 0 if ($input =~ /\bcommit\s+[0-9a-f]{12,40}/i); + $long = 1 if ($input =~ /\bcommit\s+[0-9a-f]{41,}/i); + $space = 0 if ($input =~ /\bcommit [0-9a-f]/i); + $case = 0 if ($input =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); + } elsif ($input =~ /\b([0-9a-f]{12,40})\b/i) { $orig_commit = lc($1); } - $short = 0 if ($line =~ /\bcommit\s+[0-9a-f]{12,40}/i); - $long = 1 if ($line =~ /\bcommit\s+[0-9a-f]{41,}/i); - $space = 0 if ($line =~ /\bcommit [0-9a-f]/i); - $case = 0 if ($line =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); - if ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)"\)/i) { - $orig_desc = $1; - $hasparens = 1; - } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s*$/i && - defined $rawlines[$linenr] && - $rawlines[$linenr] =~ /^\s*\("([^"]+)"\)/) { - $orig_desc = $1; - $hasparens = 1; - } elsif ($line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("[^"]+$/i && - defined $rawlines[$linenr] && - $rawlines[$linenr] =~ /^\s*[^"]+"\)/) { - $line =~ /\bcommit\s+[0-9a-f]{5,}\s+\("([^"]+)$/i; - $orig_desc = $1; - $rawlines[$linenr] =~ /^\s*([^"]+)"\)/; - $orig_desc .= " " . $1; - $hasparens = 1; - } - ($id, $description) = git_commit_info($orig_commit, $id, $orig_desc); if (defined($id) && - ($short || $long || $space || $case || ($orig_desc ne $description) || !$hasparens)) { + ($short || $long || $space || $case || ($orig_desc ne $description) || !$has_quotes) && + $last_git_commit_id_linenr != $linenr - 1) { ERROR("GIT_COMMIT_ID", - "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herecurr); + "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herectx); } + #don't report the next line if this line ends in commit and the sha1 hash is the next line + $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); } # Check for added, moved or deleted files @@ -2710,6 +3285,14 @@ sub process { "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); } +# Check for adding new DT bindings not in schema format + if (!$in_commit_log && + ($line =~ /^new file mode\s*\d+\s*$/) && + ($realfile =~ m@^Documentation/devicetree/bindings/.*\.txt$@)) { + WARN("DT_SCHEMA_BINDING_PATCH", + "DT bindings should be in DT schema format. See: Documentation/devicetree/bindings/writing-schema.rst\n"); + } + # Check for wrappage within a valid hunk of the file if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { ERROR("CORRUPTED_PATCH", @@ -2771,21 +3354,89 @@ sub process { # Check for various typo / spelling mistakes if (defined($misspellings) && ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { - while ($rawline =~ /(?:^|[^a-z@])($misspellings)(?:\b|$|[^a-z@])/gi) { + while ($rawline =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { my $typo = $1; + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); + my $hereptr = "$hereline$ptr\n"; my $typo_fix = $spelling_fix{lc($typo)}; $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); my $msg_level = \&WARN; $msg_level = \&CHK if ($file); if (&{$msg_level}("TYPO_SPELLING", - "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $herecurr) && + "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $hereptr) && $fix) { $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; } } } +# check for invalid commit id + if ($in_commit_log && $line =~ /(^fixes:|\bcommit)\s+([0-9a-f]{6,40})\b/i) { + my $id; + my $description; + ($id, $description) = git_commit_info($2, undef, undef); + if (!defined($id)) { + WARN("UNKNOWN_COMMIT_ID", + "Unknown commit id '$2', maybe rebased or not pulled?\n" . $herecurr); + } + } + +# check for repeated words separated by a single space +# avoid false positive from list command eg, '-rw-r--r-- 1 root root' + if (($rawline =~ /^\+/ || $in_commit_log) && + $rawline !~ /[bcCdDlMnpPs\?-][rwxsStT-]{9}/) { + pos($rawline) = 1 if (!$in_commit_log); + while ($rawline =~ /\b($word_pattern) (?=($word_pattern))/g) { + + my $first = $1; + my $second = $2; + my $start_pos = $-[1]; + my $end_pos = $+[2]; + if ($first =~ /(?:struct|union|enum)/) { + pos($rawline) += length($first) + length($second) + 1; + next; + } + + next if (lc($first) ne lc($second)); + next if ($first eq 'long'); + + # check for character before and after the word matches + my $start_char = ''; + my $end_char = ''; + $start_char = substr($rawline, $start_pos - 1, 1) if ($start_pos > ($in_commit_log ? 0 : 1)); + $end_char = substr($rawline, $end_pos, 1) if ($end_pos < length($rawline)); + + next if ($start_char =~ /^\S$/); + next if (index(" \t.,;?!", $end_char) == -1); + + # avoid repeating hex occurrences like 'ff ff fe 09 ...' + if ($first =~ /\b[0-9a-f]{2,}\b/i) { + next if (!exists($allow_repeated_words{lc($first)})); + } + + if (WARN("REPEATED_WORD", + "Possible repeated word: '$first'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$first $second\b/$first/; + } + } + + # if it's a repeated word on consecutive lines in a comment block + if ($prevline =~ /$;+\s*$/ && + $prevrawline =~ /($word_pattern)\s*$/) { + my $last_word = $1; + if ($rawline =~ /^\+\s*\*\s*$last_word /) { + if (WARN("REPEATED_WORD", + "Possible repeated word: '$last_word'\n" . $hereprev) && + $fix) { + $fixed[$fixlinenr] =~ s/(\+\s*\*\s*)$last_word /$1/; + } + } + } + } + # ignore non-hunk lines and lines being removed next if (!$hunk_line || $line =~ /^-/); @@ -2828,69 +3479,87 @@ sub process { # Kconfig supports named choices), so use a word boundary # (\b) rather than a whitespace character (\s) $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { - my $length = 0; - my $cnt = $realcnt; - my $ln = $linenr + 1; - my $f; - my $is_start = 0; - my $is_end = 0; - for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) { - $f = $lines[$ln - 1]; - $cnt-- if ($lines[$ln - 1] !~ /^-/); - $is_end = $lines[$ln - 1] =~ /^\+/; + my $ln = $linenr; + my $needs_help = 0; + my $has_help = 0; + my $help_length = 0; + while (defined $lines[$ln]) { + my $f = $lines[$ln++]; next if ($f =~ /^-/); - last if (!$file && $f =~ /^\@\@/); - - if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { - $is_start = 1; - } elsif ($lines[$ln - 1] =~ /^\+\s*(?:help|---help---)\s*$/) { - if ($lines[$ln - 1] =~ "---help---") { - WARN("CONFIG_DESCRIPTION", - "prefer 'help' over '---help---' for new help texts\n" . $herecurr); - } - $length = -1; + last if ($f !~ /^[\+ ]/); # !patch context + + if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { + $needs_help = 1; + next; + } + if ($f =~ /^\+\s*help\s*$/) { + $has_help = 1; + next; } - $f =~ s/^.//; - $f =~ s/#.*//; - $f =~ s/^\s+//; - next if ($f =~ /^$/); + $f =~ s/^.//; # strip patch context [+ ] + $f =~ s/#.*//; # strip # directives + $f =~ s/^\s+//; # strip leading blanks + next if ($f =~ /^$/); # skip blank lines + # At the end of this Kconfig block: # This only checks context lines in the patch # and so hopefully shouldn't trigger false # positives, even though some of these are # common words in help texts - if ($f =~ /^\s*(?:config|menuconfig|choice|endchoice| - if|endif|menu|endmenu|source)\b/x) { - $is_end = 1; + if ($f =~ /^(?:config|menuconfig|choice|endchoice| + if|endif|menu|endmenu|source)\b/x) { last; } - $length++; + $help_length++ if ($has_help); } - if ($is_start && $is_end && $length < $min_conf_desc_length) { + if ($needs_help && + $help_length < $min_conf_desc_length) { + my $stat_real = get_stat_real($linenr, $ln - 1); WARN("CONFIG_DESCRIPTION", - "please write a paragraph that describes the config symbol fully\n" . $herecurr); + "please write a help paragraph that fully describes the config symbol\n" . "$here\n$stat_real\n"); } - #print "is_start<$is_start> is_end<$is_end> length<$length>\n"; } -# check for MAINTAINERS entries that don't have the right form - if ($realfile =~ /^MAINTAINERS$/ && - $rawline =~ /^\+[A-Z]:/ && - $rawline !~ /^\+[A-Z]:\t\S/) { - if (WARN("MAINTAINERS_STYLE", - "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && - $fix) { - $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; +# check MAINTAINERS entries + if ($realfile =~ /^MAINTAINERS$/) { +# check MAINTAINERS entries for the right form + if ($rawline =~ /^\+[A-Z]:/ && + $rawline !~ /^\+[A-Z]:\t\S/) { + if (WARN("MAINTAINERS_STYLE", + "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; + } + } +# check MAINTAINERS entries for the right ordering too + my $preferred_order = 'MRLSWQBCPTFXNK'; + if ($rawline =~ /^\+[A-Z]:/ && + $prevrawline =~ /^[\+ ][A-Z]:/) { + $rawline =~ /^\+([A-Z]):\s*(.*)/; + my $cur = $1; + my $curval = $2; + $prevrawline =~ /^[\+ ]([A-Z]):\s*(.*)/; + my $prev = $1; + my $prevval = $2; + my $curindex = index($preferred_order, $cur); + my $previndex = index($preferred_order, $prev); + if ($curindex < 0) { + WARN("MAINTAINERS_STYLE", + "Unknown MAINTAINERS entry type: '$cur'\n" . $herecurr); + } else { + if ($previndex >= 0 && $curindex < $previndex) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list '$cur:' before '$prev:'\n" . $hereprev); + } elsif ((($prev eq 'F' && $cur eq 'F') || + ($prev eq 'X' && $cur eq 'X')) && + ($prevval cmp $curval) > 0) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list file patterns in alphabetic order\n" . $hereprev); + } + } } - } - -# discourage the use of boolean for type definition attributes of Kconfig options - if ($realfile =~ /Kconfig/ && - $line =~ /^\+\s*\bboolean\b/) { - WARN("CONFIG_TYPE_BOOLEAN", - "Use of boolean is deprecated, please use bool instead.\n" . $herecurr); } if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) && @@ -2915,7 +3584,7 @@ sub process { my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; my $dt_path = $root . "/Documentation/devicetree/bindings/"; - my $vp_file = $dt_path . "vendor-prefixes.txt"; + my $vp_file = $dt_path . "vendor-prefixes.yaml"; foreach my $compat (@compats) { my $compat2 = $compat; @@ -2930,7 +3599,7 @@ sub process { next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; my $vendor = $1; - `grep -Eq "^$vendor\\b" $vp_file`; + `grep -Eq "\\"\\^\Q$vendor\E,\\.\\*\\":" $vp_file`; if ( $? >> 8 ) { WARN("UNDOCUMENTED_DT_STRING", "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); @@ -2948,23 +3617,62 @@ sub process { $comment = '/*'; } elsif ($realfile =~ /\.(c|dts|dtsi)$/) { $comment = '//'; - } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc)$/) { + } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { $comment = '#'; } elsif ($realfile =~ /\.rst$/) { $comment = '..'; } +# check SPDX comment style for .[chsS] files + if ($realfile =~ /\.[chsS]$/ && + $rawline =~ /SPDX-License-Identifier:/ && + $rawline !~ m@^\+\s*\Q$comment\E\s*@) { + WARN("SPDX_LICENSE_TAG", + "Improper SPDX comment style for '$realfile', please use '$comment' instead\n" . $herecurr); + } + if ($comment !~ /^$/ && - $rawline !~ /^\+\Q$comment\E SPDX-License-Identifier: /) { + $rawline !~ m@^\+\Q$comment\E SPDX-License-Identifier: @) { WARN("SPDX_LICENSE_TAG", "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); + } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { + my $spdx_license = $1; + if (!is_SPDX_License_valid($spdx_license)) { + WARN("SPDX_LICENSE_TAG", + "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); + } + if ($realfile =~ m@^Documentation/devicetree/bindings/@ && + not $spdx_license =~ /GPL-2\.0.*BSD-2-Clause/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("SPDX_LICENSE_TAG", + + "DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; + } + } } } } +# check for embedded filenames + if ($rawline =~ /^\+.*\Q$realfile\E/) { + WARN("EMBEDDED_FILENAME", + "It's generally not useful to have the filename in the file\n" . $herecurr); + } + # check we are in a valid source file if not then ignore this hunk next if ($realfile !~ /\.(h|c|s|S|sh|dtsi|dts)$/); +# check for using SPDX-License-Identifier on the wrong line number + if ($realline != $checklicenseline && + $rawline =~ /\bSPDX-License-Identifier:/ && + substr($line, @-, @+ - @-) eq "$;" x (@+ - @-)) { + WARN("SPDX_LICENSE_TAG", + "Misplaced SPDX-License-Identifier tag - use line $checklicenseline instead\n" . $herecurr); + } + # line length limit (with some exclusions) # # There are a few types of lines that may extend beyond $max_line_length: @@ -3022,22 +3730,34 @@ sub process { if ($msg_type ne "" && (show_type("LONG_LINE") || show_type($msg_type))) { - WARN($msg_type, - "line over $max_line_length characters\n" . $herecurr); + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}($msg_type, + "line length of $length exceeds $max_line_length columns\n" . $herecurr); } } # check for adding lines without a newline. if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { - WARN("MISSING_EOF_NEWLINE", - "adding a line without newline at end of file\n" . $herecurr); + if (WARN("MISSING_EOF_NEWLINE", + "adding a line without newline at end of file\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr+1, "No newline at end of file"); + } + } + +# check for .L prefix local symbols in .S files + if ($realfile =~ /\.S$/ && + $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { + WARN("AVOID_L_PREFIX", + "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/asm-annotations.rst\n" . $herecurr); } # check we are in a valid source file C or perl if not then ignore this hunk next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); # at the beginning of a line any tabs must come first and anything -# more than 8 must use tabs. +# more than $tabsize must use tabs. if ($rawline =~ /^\+\s* \t\s*\S/ || $rawline =~ /^\+\s* \s*/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; @@ -3056,7 +3776,7 @@ sub process { "please, no space before tabs\n" . $herevet) && $fix) { while ($fixed[$fixlinenr] =~ - s/(^\+.*) {8,8}\t/$1\t\t/) {} + s/(^\+.*) {$tabsize,$tabsize}\t/$1\t\t/) {} while ($fixed[$fixlinenr] =~ s/(^\+.*) +\t/$1\t/) {} } @@ -3064,31 +3784,45 @@ sub process { # check for assignments on the start of a line if ($sline =~ /^\+\s+($Assignment)[^=]/) { - CHK("ASSIGNMENT_CONTINUATIONS", - "Assignment operator '$1' should be on the previous line\n" . $hereprev); + my $operator = $1; + if (CHK("ASSIGNMENT_CONTINUATIONS", + "Assignment operator '$1' should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # add assignment operator to the previous line, remove from current line + $fixed[$fixlinenr - 1] .= " $operator"; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } } # check for && or || at the start of a line if ($rawline =~ /^\+\s*(&&|\|\|)/) { - CHK("LOGICAL_CONTINUATIONS", - "Logical continuations should be on the previous line\n" . $hereprev); + my $operator = $1; + if (CHK("LOGICAL_CONTINUATIONS", + "Logical continuations should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # insert logical operator at last non-comment, non-whitepsace char on previous line + $prevline =~ /[\s$;]*$/; + my $line_end = substr($prevrawline, $-[0]); + $fixed[$fixlinenr - 1] =~ s/\Q$line_end\E$/ $operator$line_end/; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } } # check indentation starts on a tab stop - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { my $indent = length($1); - if ($indent % 8) { + if ($indent % $tabsize) { if (WARN("TABSTOP", "Statements should start on a tabstop\n" . $herecurr) && $fix) { - $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/8)@e; + $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/$tabsize)@e; } } } # check multi-line statement indentation matches previous line - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { $prevline =~ /^\+(\t*)(.*)$/; my $oldindent = $1; @@ -3100,8 +3834,8 @@ sub process { my $newindent = $2; my $goodtabindent = $oldindent . - "\t" x ($pos / 8) . - " " x ($pos % 8); + "\t" x ($pos / $tabsize) . + " " x ($pos % $tabsize); my $goodspaceindent = $oldindent . " " x $pos; if ($newindent ne $goodtabindent && @@ -3139,7 +3873,7 @@ sub process { if ($realfile =~ m@^(drivers/net/|net/)@ && $prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ && $rawline =~ /^\+[ \t]*\*/ && - $realline > 2) { + $realline > 3) { # Do not warn about the initial copyright comment block after SPDX-License-Identifier WARN("NETWORKING_BLOCK_COMMENT_STYLE", "networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev); } @@ -3221,43 +3955,48 @@ sub process { } # check for missing blank lines after declarations - if ($sline =~ /^\+\s+\S/ && #Not at char 1 - # actual declarations - ($prevline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || +# (declarations must have the same indentation and not be at the start of line) + if (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/) { + # use temporaries + my $sl = $sline; + my $pl = $prevline; + # remove $Attribute/$Sparse uses to simplify comparisons + $sl =~ s/\b(?:$Attribute|$Sparse)\b//g; + $pl =~ s/\b(?:$Attribute|$Sparse)\b//g; + if (($pl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || # function pointer declarations - $prevline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + $pl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || # foo bar; where foo is some local typedef or #define - $prevline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + $pl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || # known declaration macros - $prevline =~ /^\+\s+$declaration_macros/) && + $pl =~ /^\+\s+$declaration_macros/) && # for "else if" which can look like "$Ident $Ident" - !($prevline =~ /^\+\s+$c90_Keywords\b/ || + !($pl =~ /^\+\s+$c90_Keywords\b/ || # other possible extensions of declaration lines - $prevline =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || + $pl =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || # not starting a section or a macro "\" extended line - $prevline =~ /(?:\{\s*|\\)$/) && + $pl =~ /(?:\{\s*|\\)$/) && # looks like a declaration - !($sline =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + !($sl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || # function pointer declarations - $sline =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + $sl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || # foo bar; where foo is some local typedef or #define - $sline =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + $sl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || # known declaration macros - $sline =~ /^\+\s+$declaration_macros/ || + $sl =~ /^\+\s+$declaration_macros/ || # start of struct or union or enum - $sline =~ /^\+\s+(?:union|struct|enum|typedef)\b/ || + $sl =~ /^\+\s+(?:static\s+)?(?:const\s+)?(?:union|struct|enum|typedef)\b/ || # start or end of block or continuation of declaration - $sline =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || + $sl =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || # bitfield continuation - $sline =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || + $sl =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || # other possible extensions of declaration lines - $sline =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/) && - # indentation of previous and current line are the same - (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/)) { - if (WARN("LINE_SPACING", - "Missing a blank line after declarations\n" . $hereprev) && - $fix) { - fix_insert_line($fixlinenr, "\+"); + $sl =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/)) { + if (WARN("LINE_SPACING", + "Missing a blank line after declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } } } @@ -3310,12 +4049,16 @@ sub process { } # check indentation of a line with a break; -# if the previous line is a goto or return and is indented the same # of tabs +# if the previous line is a goto, return or break +# and is indented the same # of tabs if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { my $tabs = $1; - if ($prevline =~ /^\+$tabs(?:goto|return)\b/) { - WARN("UNNECESSARY_BREAK", - "break is not useful after a goto or return\n" . $hereprev); + if ($prevline =~ /^\+$tabs(goto|return|break)\b/) { + if (WARN("UNNECESSARY_BREAK", + "break is not useful after a $1\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } } } @@ -3572,11 +4315,11 @@ sub process { #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; if ($check && $s ne '' && - (($sindent % 8) != 0 || + (($sindent % $tabsize) != 0 || ($sindent < $indent) || ($sindent == $indent && ($s !~ /^\s*(?:\}|\{|else\b)/)) || - ($sindent > $indent + 8))) { + ($sindent > $indent + $tabsize))) { WARN("SUSPECT_CODE_INDENT", "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); } @@ -3598,6 +4341,17 @@ sub process { #ignore lines not being added next if ($line =~ /^[^\+]/); +# check for self assignments used to avoid compiler warnings +# e.g.: int foo = foo, *bar = NULL; +# struct foo bar = *(&(bar)); + if ($line =~ /^\+\s*(?:$Declare)?([A-Za-z_][A-Za-z\d_]*)\s*=/) { + my $var = $1; + if ($line =~ /^\+\s*(?:$Declare)?$var\s*=\s*(?:$var|\*\s*\(?\s*&\s*\(?\s*$var\s*\)?\s*\)?)\s*[;,]/) { + WARN("SELF_ASSIGNMENT", + "Do not use self-assignments to avoid compiler warnings\n" . $herecurr); + } + } + # check for dereferences that span multiple lines if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { @@ -3713,13 +4467,13 @@ sub process { if (defined $realline_next && exists $lines[$realline_next - 1] && !defined $suppress_export{$realline_next} && - ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/ || - $lines[$realline_next - 1] =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { + ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/)) { # Handle definitions which produce identifiers with # a prefix: # XXX(foo); # EXPORT_SYMBOL(something_foo); my $name = $1; + $name =~ s/^\s*($Ident).*/$1/; if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && $name =~ /^${Ident}_$2/) { #print "FOO C name<$name>\n"; @@ -3741,8 +4495,7 @@ sub process { } if (!defined $suppress_export{$linenr} && $prevline =~ /^.\s*$/ && - ($line =~ /EXPORT_SYMBOL.*\((.*)\)/ || - $line =~ /EXPORT_UNUSED_SYMBOL.*\((.*)\)/)) { + ($line =~ /EXPORT_SYMBOL.*\((.*)\)/)) { #print "FOO B <$lines[$linenr - 1]>\n"; $suppress_export{$linenr} = 2; } @@ -3753,7 +4506,8 @@ sub process { } # check for global initialisers. - if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/) { + if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/ && + !exclude_global_initialisers($realfile)) { if (ERROR("GLOBAL_INITIALISERS", "do not initialise globals to $1\n" . $herecurr) && $fix) { @@ -3777,19 +4531,48 @@ sub process { "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); } +# check for unnecessary <signed> int declarations of short/long/long long + while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { + my $type = trim($1); + next if ($type !~ /\bint\b/); + next if ($type !~ /\b(?:short|long\s+long|long)\b/); + my $new_type = $type; + $new_type =~ s/\b\s*int\s*\b/ /; + $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; + $new_type =~ s/^const\s+//; + $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); + $new_type = "const $new_type" if ($type =~ /^const\b/); + $new_type =~ s/\s+/ /g; + $new_type = trim($new_type); + if (WARN("UNNECESSARY_INT", + "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; + } + } + # check for static const char * arrays. if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { WARN("STATIC_CONST_CHAR_ARRAY", "static const char * array should probably be static const char * const\n" . $herecurr); - } + } + +# check for initialized const char arrays that should be static const + if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { + if (WARN("STATIC_CONST_CHAR_ARRAY", + "const array should probably be static const\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; + } + } # check for static char foo[] = "bar" declarations. if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { WARN("STATIC_CONST_CHAR_ARRAY", "static char array declaration should probably be static const char\n" . $herecurr); - } + } # check for const <foo> const where <foo> is not a pointer or array type if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { @@ -3803,12 +4586,24 @@ sub process { } } +# check for const static or static <non ptr type> const declarations +# prefer 'static const <foo>' over 'const static <foo>' and 'static <foo> const' + if ($sline =~ /^\+\s*const\s+static\s+($Type)\b/ || + $sline =~ /^\+\s*static\s+($BasicType)\s+const\b/) { + if (WARN("STATIC_CONST", + "Move const after static - use 'static const $1'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bconst\s+static\b/static const/; + $fixed[$fixlinenr] =~ s/\bstatic\s+($BasicType)\s+const\b/static const $1/; + } + } + # check for non-global char *foo[] = {"bar", ...} declarations. if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { WARN("STATIC_CONST_CHAR_ARRAY", "char * array declaration might be better as static const\n" . $herecurr); - } + } # check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { @@ -3824,7 +4619,7 @@ sub process { } # check for function declarations without arguments like "int foo()" - if ($line =~ /(\b$Type\s+$Ident)\s*\(\s*\)/) { + if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { if (ERROR("FUNCTION_WITHOUT_ARGS", "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && $fix) { @@ -3925,25 +4720,23 @@ sub process { "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); } - if ($line =~ /\bprintk\s*\(\s*KERN_([A-Z]+)/) { - my $orig = $1; +# prefer variants of (subsystem|netdev|dev|pr)_<level> to printk(KERN_<LEVEL> + if ($line =~ /\b(printk(_once|_ratelimited)?)\s*\(\s*KERN_([A-Z]+)/) { + my $printk = $1; + my $modifier = $2; + my $orig = $3; + $modifier = "" if (!defined($modifier)); my $level = lc($orig); $level = "warn" if ($level eq "warning"); my $level2 = $level; $level2 = "dbg" if ($level eq "debug"); + $level .= $modifier; + $level2 .= $modifier; WARN("PREFER_PR_LEVEL", - "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to printk(KERN_$orig ...\n" . $herecurr); - } - - if ($line =~ /\bpr_warning\s*\(/) { - if (WARN("PREFER_PR_LEVEL", - "Prefer pr_warn(... to pr_warning(...\n" . $herecurr) && - $fix) { - $fixed[$fixlinenr] =~ - s/\bpr_warning\b/pr_warn/; - } + "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to $printk(KERN_$orig ...\n" . $herecurr); } +# prefer dev_<level> to dev_printk(KERN_<LEVEL> if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { my $orig = $1; my $level = lc($orig); @@ -3953,6 +4746,12 @@ sub process { "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); } +# trace_printk should not be used in production code. + if ($line =~ /\b(trace_printk|trace_puts|ftrace_vprintk)\s*\(/) { + WARN("TRACE_PRINTK", + "Do not use $1() in production code (this can be ignored if built only with a debug config option)\n" . $herecurr); + } + # ENOSYS means "bad syscall nr" and nothing else. This will have a small # number of false positives, but assembly files are not checked, so at # least the arch entry code will not trigger this warning. @@ -3961,9 +4760,20 @@ sub process { "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); } +# ENOTSUPP is not a standard error code and should be avoided in new patches. +# Folks usually mean EOPNOTSUPP (also called ENOTSUP), when they type ENOTSUPP. +# Similarly to ENOSYS warning a small number of false positives is expected. + if (!$file && $line =~ /\bENOTSUPP\b/) { + if (WARN("ENOTSUPP", + "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bENOTSUPP\b/EOPNOTSUPP/; + } + } + # function brace can't be on same line, except for #defines of do while, # or if closed on same line - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && $sline !~ /\#\s*define\b.*do\s*\{/ && $sline !~ /}/) { @@ -3972,7 +4782,7 @@ sub process { $fix) { fix_delete_line($fixlinenr, $rawline); my $fixed_line = $rawline; - $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*){(.*)$/; + $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*)\{(.*)$/; my $line1 = $1; my $line2 = $2; fix_insert_line($fixlinenr, ltrim($line1)); @@ -4383,7 +5193,7 @@ sub process { # A colon needs no spaces before when it is # terminating a case value or a label. } elsif ($opv eq ':C' || $opv eq ':L') { - if ($ctx =~ /Wx./) { + if ($ctx =~ /Wx./ and $realfile !~ m@.*\.lds\.h$@) { if (ERROR("SPACING", "space prohibited before that '$op' $at\n" . $hereptr)) { $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); @@ -4401,7 +5211,7 @@ sub process { ($op eq '>' && $ca =~ /<\S+\@\S+$/)) { - $ok = 1; + $ok = 1; } # for asm volatile statements @@ -4467,7 +5277,7 @@ sub process { ## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { ## ## # Remove any bracketed sections to ensure we do not -## # falsly report the parameters of functions. +## # falsely report the parameters of functions. ## my $ln = $line; ## while ($ln =~ s/\([^\(\)]*\)//g) { ## } @@ -4479,11 +5289,11 @@ sub process { #need space before brace following if, while, etc if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || - $line =~ /do\{/) { + $line =~ /\b(?:else|do)\{/) { if (ERROR("SPACING", "space required before the open brace '{'\n" . $herecurr) && $fix) { - $fixed[$fixlinenr] =~ s/^(\+.*(?:do|\)))\{/$1 {/; + $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; } } @@ -4497,7 +5307,7 @@ sub process { # closing brace should have a space following it when it has anything # on the line - if ($line =~ /}(?!(?:,|;|\)))\S/) { + if ($line =~ /}(?!(?:,|;|\)|\}))\S/) { if (ERROR("SPACING", "space required after that close brace '}'\n" . $herecurr) && $fix) { @@ -4574,7 +5384,7 @@ sub process { # check for unnecessary parentheses around comparisons in if uses # when !drivers/staging or command-line uses --strict if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) && - $^V && $^V ge 5.10.0 && defined($stat) && + $perl_version_ok && defined($stat) && $stat =~ /(^.\s*if\s*($balanced_parens))/) { my $if_stat = $1; my $test = substr($2, 1, -1); @@ -4597,9 +5407,13 @@ sub process { } } -#goto labels aren't indented, allow a single space however - if ($line=~/^.\s+[A-Za-z\d_]+:(?![0-9]+)/ and - !($line=~/^. [A-Za-z\d_]+:/) and !($line=~/^.\s+default:/)) { +# check that goto labels aren't indented (allow a single space indentation) +# and ignore bitfield definitions like foo:1 +# Strictly, labels can have whitespace after the identifier and before the : +# but this is not allowed here as many ?: uses would appear to be labels + if ($sline =~ /^.\s+[A-Za-z_][A-Za-z\d_]*:(?!\s*\d+)/ && + $sline !~ /^. [A-Za-z\d_][A-Za-z\d_]*:/ && + $sline !~ /^.\s+default:/) { if (WARN("INDENTED_LABEL", "labels should not be indented\n" . $herecurr) && $fix) { @@ -4608,10 +5422,21 @@ sub process { } } +# check if a statement with a comma should be two statements like: +# foo = bar(), /* comma should be semicolon */ +# bar = baz(); + if (defined($stat) && + $stat =~ /^\+\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*,\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*;\s*$/) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + WARN("SUSPECT_COMMA_SEMICOLON", + "Possible comma where semicolon could be used\n" . $herectx); + } + # return is not a function if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { my $spacing = $1; - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { my $value = $1; $value = deparenthesize($value); @@ -4635,10 +5460,10 @@ sub process { $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { WARN("RETURN_VOID", "void function return statements are not generally useful\n" . $hereprev); - } + } # if statements using unnecessary parentheses - ie: if ((foo == bar)) - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $line =~ /\bif\s*((?:\(\s*){2,})/) { my $openparens = $1; my $count = $openparens =~ tr@\(@\(@; @@ -4655,7 +5480,7 @@ sub process { # avoid cases like "foo + BAR < baz" # only fix matches surrounded by parentheses to avoid incorrect # conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { my $lead = $1; my $const = $2; @@ -4683,7 +5508,7 @@ sub process { # Return of what appears to be an errno should normally be negative if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { my $name = $1; - if ($name ne 'EOF' && $name ne 'ERROR') { + if ($name ne 'EOF' && $name ne 'ERROR' && $name !~ /^EPOLL/) { WARN("USE_NEGATIVE_ERRNO", "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); } @@ -4728,15 +5553,37 @@ sub process { my ($s, $c) = ($stat, $cond); if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { - ERROR("ASSIGN_IN_IF", - "do not use assignment in if condition\n" . $herecurr); + if (ERROR("ASSIGN_IN_IF", + "do not use assignment in if condition\n" . $herecurr) && + $fix && $perl_version_ok) { + if ($rawline =~ /^\+(\s+)if\s*\(\s*(\!)?\s*\(\s*(($Lval)\s*=\s*$LvalOrFunc)\s*\)\s*(?:($Compare)\s*($FuncArg))?\s*\)\s*(\{)?\s*$/) { + my $space = $1; + my $not = $2; + my $statement = $3; + my $assigned = $4; + my $test = $8; + my $against = $9; + my $brace = $15; + fix_delete_line($fixlinenr, $rawline); + fix_insert_line($fixlinenr, "$space$statement;"); + my $newline = "${space}if ("; + $newline .= '!' if defined($not); + $newline .= '(' if (defined $not && defined($test) && defined($against)); + $newline .= "$assigned"; + $newline .= " $test $against" if (defined($test) && defined($against)); + $newline .= ')' if (defined $not && defined($test) && defined($against)); + $newline .= ')'; + $newline .= " {" if (defined($brace)); + fix_insert_line($fixlinenr + 1, $newline); + } + } } # Find out what is on the end of the line after the # conditional. substr($s, 0, length($c), ''); $s =~ s/\n.*//g; - $s =~ s/$;//g; # Remove any comments + $s =~ s/$;//g; # Remove any comments if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && $c !~ /}\s*while\s*/) { @@ -4775,7 +5622,7 @@ sub process { # if and else should not have general statements after it if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { my $s = $1; - $s =~ s/$;//g; # Remove any comments + $s =~ s/$;//g; # Remove any comments if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr); @@ -4847,24 +5694,16 @@ sub process { while ($line =~ m{($Constant|$Lval)}g) { my $var = $1; -#gcc binary extension - if ($var =~ /^$Binary$/) { - if (WARN("GCC_BINARY_CONSTANT", - "Avoid gcc v4.3+ binary constant extension: <$var>\n" . $herecurr) && - $fix) { - my $hexval = sprintf("0x%x", oct($var)); - $fixed[$fixlinenr] =~ - s/\b$var\b/$hexval/; - } - } - #CamelCase if ($var !~ /^$Constant$/ && $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && +#Ignore some autogenerated defines and enum values + $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && #Ignore Page<foo> variants $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && -#Ignore SI style variants like nS, mV and dB (ie: max_uV, regulator_min_uA_show) - $var !~ /^(?:[a-z_]*?)_?[a-z][A-Z](?:_[a-z_]+)?$/ && +#Ignore SI style variants like nS, mV and dB +#(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) + $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && #Ignore some three character SI units explicitly, like MiB and KHz $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { while ($var =~ m{($Ident)}g) { @@ -4945,6 +5784,7 @@ sub process { if (defined $define_args && $define_args ne "") { $define_args = substr($define_args, 1, length($define_args) - 2); $define_args =~ s/\s*//g; + $define_args =~ s/\\\+?//g; @def_args = split(",", $define_args); } @@ -4954,13 +5794,13 @@ sub process { $dstat =~ s/\s*$//s; # Flatten any parentheses and braces - while ($dstat =~ s/\([^\(\)]*\)/1/ || - $dstat =~ s/\{[^\{\}]*\}/1/ || - $dstat =~ s/.\[[^\[\]]*\]/1/) + while ($dstat =~ s/\([^\(\)]*\)/1u/ || + $dstat =~ s/\{[^\{\}]*\}/1u/ || + $dstat =~ s/.\[[^\[\]]*\]/1u/) { } - # Flatten any obvious string concatentation. + # Flatten any obvious string concatenation. while ($dstat =~ s/($String)\s*$Ident/$1/ || $dstat =~ s/$Ident\s*($String)/$1/) { @@ -4997,6 +5837,7 @@ sub process { $dstat !~ /^\.$Ident\s*=/ && # .foo = $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) + $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} $dstat !~ /^for\s*$Constant$/ && # for (...) $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() $dstat !~ /^do\s*{/ && # do {... @@ -5038,7 +5879,7 @@ sub process { next if ($arg =~ /\.\.\./); next if ($arg =~ /^type$/i); my $tmp_stmt = $define_stmt; - $tmp_stmt =~ s/\b(typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; + $tmp_stmt =~ s/\b(__must_be_array|offsetof|sizeof|sizeof_field|__stringify|typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; $tmp_stmt =~ s/\#+\s*$arg\b//g; $tmp_stmt =~ s/\b$arg\s*\#\#//g; my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; @@ -5080,7 +5921,7 @@ sub process { # do {} while (0) macro tests: # single-statement macros do not need to be enclosed in do while (0) loop, # macro should not end with a semicolon - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { my $ln = $linenr; @@ -5121,16 +5962,6 @@ sub process { } } -# make sure symbols are always wrapped with VMLINUX_SYMBOL() ... -# all assignments may have only one of the following with an assignment: -# . -# ALIGN(...) -# VMLINUX_SYMBOL(...) - if ($realfile eq 'vmlinux.lds.h' && $line =~ /(?:(?:^|\s)$Ident\s*=|=\s*$Ident(?:\s|$))/) { - WARN("MISSING_VMLINUX_SYMBOL", - "vmlinux.lds.h needs VMLINUX_SYMBOL() around C-visible symbols\n" . $herecurr); - } - # check for redundant bracing round if etc if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { my ($level, $endln, @chunks) = @@ -5325,6 +6156,17 @@ sub process { "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); } +# check for unnecessary function tracing like uses +# This does not use $logFunctions because there are many instances like +# 'dprintk(FOO, "%s()\n", __func__);' which do not match $logFunctions + if ($rawline =~ /^\+.*\([^"]*"$tracing_logging_tags{0,3}%s(?:\s*\(\s*\)\s*)?$tracing_logging_tags{0,3}(?:\\n)?"\s*,\s*__func__\s*\)\s*;/) { + if (WARN("TRACING_LOGGING", + "Unnecessary ftrace-like logging - prefer using ftrace\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + # check for spaces before a quoted newline if ($rawline =~ /^.*\".*\s\\n/) { if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", @@ -5336,15 +6178,29 @@ sub process { } # concatenated string without spaces between elements - if ($line =~ /$String[A-Z_]/ || $line =~ /[A-Za-z0-9_]$String/) { - CHK("CONCATENATED_STRING", - "Concatenated strings should use spaces between elements\n" . $herecurr); + if ($line =~ /$String[A-Z_]/ || + ($line =~ /([A-Za-z0-9_]+)$String/ && $1 !~ /^[Lu]$/)) { + if (CHK("CONCATENATED_STRING", + "Concatenated strings should use spaces between elements\n" . $herecurr) && + $fix) { + while ($line =~ /($String)/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; + $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; + } + } } # uncoalesced string fragments - if ($line =~ /$String\s*"/) { - WARN("STRING_FRAGMENTS", - "Consecutive strings are generally better as a single string\n" . $herecurr); + if ($line =~ /$String\s*[Lu]?"/) { + if (WARN("STRING_FRAGMENTS", + "Consecutive strings are generally better as a single string\n" . $herecurr) && + $fix) { + while ($line =~ /($String)(?=\s*")/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; + } + } } # check for non-standard and hex prefixed decimal printf formats @@ -5380,9 +6236,14 @@ sub process { # warn about #if 0 if ($line =~ /^.\s*\#\s*if\s+0\b/) { - CHK("REDUNDANT_CODE", - "if this code is redundant consider removing it\n" . - $herecurr); + WARN("IF_0", + "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); + } + +# warn about #if 1 + if ($line =~ /^.\s*\#\s*if\s+1\b/) { + WARN("IF_1", + "Consider removing the #if 1 and its #endif\n" . $herecurr); } # check for needless "if (<foo>) fn(<foo>)" uses @@ -5429,7 +6290,8 @@ sub process { my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); # print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); - if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*(?:devm_)?(?:[kv][czm]alloc(?:_node|_array)?\b|kstrdup|kmemdup|(?:dev_)?alloc_skb)/) { + if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*$allocFunctions\s*\(/ && + $s !~ /\b__GFP_NOWARN\b/ ) { WARN("OOM_MESSAGE", "Possible unnecessary 'out of memory' message\n" . $hereprev); } @@ -5452,8 +6314,30 @@ sub process { "Avoid logging continuation uses where feasible\n" . $herecurr); } +# check for unnecessary use of %h[xudi] and %hh[xudi] in logging functions + if (defined $stat && + $line =~ /\b$logFunctions\s*\(/ && + index($stat, '"') >= 0) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + pos($stat_real) = index($stat_real, '"'); + while ($stat_real =~ /[^\"%]*(%[\#\d\.\*\-]*(h+)[idux])/g) { + my $pspec = $1; + my $h = $2; + my $lineoff = substr($stat_real, 0, $-[1]) =~ tr@\n@@; + if (WARN("UNNECESSARY_MODIFIER", + "Integer promotion: Using '$h' in '$pspec' is unnecessary\n" . "$here\n$stat_real\n") && + $fix && $fixed[$fixlinenr + $lineoff] =~ /^\+/) { + my $nspec = $pspec; + $nspec =~ s/h//g; + $fixed[$fixlinenr + $lineoff] =~ s/\Q$pspec\E/$nspec/; + } + } + } + # check for mask then right shift without a parentheses - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so WARN("MASK_THEN_SHIFT", @@ -5461,7 +6345,7 @@ sub process { } # check for pointer comparisons to NULL - if ($^V && $^V ge 5.10.0) { + if ($perl_version_ok) { while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { my $val = $1; my $equal = "!"; @@ -5550,7 +6434,7 @@ sub process { # ignore udelay's < 10, however if (! ($delay < 10) ) { CHK("USLEEP_RANGE", - "usleep_range is preferred over udelay; see Documentation/timers/timers-howto.txt\n" . $herecurr); + "usleep_range is preferred over udelay; see Documentation/timers/timers-howto.rst\n" . $herecurr); } if ($delay > 2000) { WARN("LONG_UDELAY", @@ -5562,7 +6446,7 @@ sub process { if ($line =~ /\bmsleep\s*\((\d+)\);/) { if ($1 < 20) { WARN("MSLEEP", - "msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.txt\n" . $herecurr); + "msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.rst\n" . $herecurr); } } @@ -5610,8 +6494,7 @@ sub process { my $barriers = qr{ mb| rmb| - wmb| - read_barrier_depends + wmb }x; my $barrier_stems = qr{ mb__before_atomic| @@ -5652,10 +6535,12 @@ sub process { } } -# check for smp_read_barrier_depends and read_barrier_depends - if (!$file && $line =~ /\b(smp_|)read_barrier_depends\s*\(/) { - WARN("READ_BARRIER_DEPENDS", - "$1read_barrier_depends should only be used in READ_ONCE or DEC Alpha code\n" . $herecurr); +# check for data_race without a comment. + if ($line =~ /\bdata_race\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("DATA_RACE", + "data_race without comment\n" . $herecurr); + } } # check of hardware specific defines @@ -5697,43 +6582,73 @@ sub process { } } -# Check for __attribute__ packed, prefer __packed - if ($realfile !~ m@\binclude/uapi/@ && - $line =~ /\b__attribute__\s*\(\s*\(.*\bpacked\b/) { - WARN("PREFER_PACKED", - "__packed is preferred over __attribute__((packed))\n" . $herecurr); - } - -# Check for __attribute__ aligned, prefer __aligned - if ($realfile !~ m@\binclude/uapi/@ && - $line =~ /\b__attribute__\s*\(\s*\(.*aligned/) { - WARN("PREFER_ALIGNED", - "__aligned(size) is preferred over __attribute__((aligned(size)))\n" . $herecurr); - } - -# Check for __attribute__ format(printf, prefer __printf +# Check for compiler attributes if ($realfile !~ m@\binclude/uapi/@ && - $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf/) { - if (WARN("PREFER_PRINTF", - "__printf(string-index, first-to-check) is preferred over __attribute__((format(printf, string-index, first-to-check)))\n" . $herecurr) && - $fix) { - $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*printf\s*,\s*(.*)\)\s*\)\s*\)/"__printf(" . trim($1) . ")"/ex; - + $rawline =~ /\b__attribute__\s*\(\s*($balanced_parens)\s*\)/) { + my $attr = $1; + $attr =~ s/\s*\(\s*(.*)\)\s*/$1/; + + my %attr_list = ( + "alias" => "__alias", + "aligned" => "__aligned", + "always_inline" => "__always_inline", + "assume_aligned" => "__assume_aligned", + "cold" => "__cold", + "const" => "__attribute_const__", + "copy" => "__copy", + "designated_init" => "__designated_init", + "externally_visible" => "__visible", + "format" => "printf|scanf", + "gnu_inline" => "__gnu_inline", + "malloc" => "__malloc", + "mode" => "__mode", + "no_caller_saved_registers" => "__no_caller_saved_registers", + "noclone" => "__noclone", + "noinline" => "noinline", + "nonstring" => "__nonstring", + "noreturn" => "__noreturn", + "packed" => "__packed", + "pure" => "__pure", + "section" => "__section", + "used" => "__used", + "weak" => "__weak" + ); + + while ($attr =~ /\s*(\w+)\s*(${balanced_parens})?/g) { + my $orig_attr = $1; + my $params = ''; + $params = $2 if defined($2); + my $curr_attr = $orig_attr; + $curr_attr =~ s/^[\s_]+|[\s_]+$//g; + if (exists($attr_list{$curr_attr})) { + my $new = $attr_list{$curr_attr}; + if ($curr_attr eq "format" && $params) { + $params =~ /^\s*\(\s*(\w+)\s*,\s*(.*)/; + $new = "__$1\($2"; + } else { + $new = "$new$params"; + } + if (WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "Prefer $new over __attribute__(($orig_attr$params))\n" . $herecurr) && + $fix) { + my $remove = "\Q$orig_attr\E" . '\s*' . "\Q$params\E" . '(?:\s*,\s*)?'; + $fixed[$fixlinenr] =~ s/$remove//; + $fixed[$fixlinenr] =~ s/\b__attribute__/$new __attribute__/; + $fixed[$fixlinenr] =~ s/\}\Q$new\E/} $new/; + $fixed[$fixlinenr] =~ s/ __attribute__\s*\(\s*\(\s*\)\s*\)//; + } + } } - } -# Check for __attribute__ format(scanf, prefer __scanf - if ($realfile !~ m@\binclude/uapi/@ && - $line =~ /\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\b/) { - if (WARN("PREFER_SCANF", - "__scanf(string-index, first-to-check) is preferred over __attribute__((format(scanf, string-index, first-to-check)))\n" . $herecurr) && - $fix) { - $fixed[$fixlinenr] =~ s/\b__attribute__\s*\(\s*\(\s*format\s*\(\s*scanf\s*,\s*(.*)\)\s*\)\s*\)/"__scanf(" . trim($1) . ")"/ex; + # Check for __attribute__ unused, prefer __always_unused or __maybe_unused + if ($attr =~ /^_*unused/) { + WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "__always_unused or __maybe_unused is preferred over __attribute__((__unused__))\n" . $herecurr); } } # Check for __attribute__ weak, or __weak declarations (may have link issues) - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || $line =~ /\b__weak\b/)) { @@ -5764,18 +6679,18 @@ sub process { if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { my $cast = $1; my $const = $2; + my $suffix = ""; + my $newconst = $const; + $newconst =~ s/${Int_type}$//; + $suffix .= 'U' if ($cast =~ /\bunsigned\b/); + if ($cast =~ /\blong\s+long\b/) { + $suffix .= 'LL'; + } elsif ($cast =~ /\blong\b/) { + $suffix .= 'L'; + } if (WARN("TYPECAST_INT_CONSTANT", - "Unnecessary typecast of c90 int constant\n" . $herecurr) && + "Unnecessary typecast of c90 int constant - '$cast$const' could be '$const$suffix'\n" . $herecurr) && $fix) { - my $suffix = ""; - my $newconst = $const; - $newconst =~ s/${Int_type}$//; - $suffix .= 'U' if ($cast =~ /\bunsigned\b/); - if ($cast =~ /\blong\s+long\b/) { - $suffix .= 'LL'; - } elsif ($cast =~ /\blong\b/) { - $suffix .= 'L'; - } $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; } } @@ -5815,25 +6730,31 @@ sub process { } # check for vsprintf extension %p<foo> misuses - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && $1 !~ /^_*volatile_*$/) { - my $specifier; - my $extension; - my $bad_specifier = ""; my $stat_real; my $lc = $stat =~ tr@\n@@; $lc = $lc + $linenr; for (my $count = $linenr; $count <= $lc; $count++) { + my $specifier; + my $extension; + my $qualifier; + my $bad_specifier = ""; my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); $fmt =~ s/%%//g; - while ($fmt =~ /(\%[\*\d\.]*p(\w))/g) { + while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) { $specifier = $1; $extension = $2; - if ($extension !~ /[SsBKRraEhMmIiUDdgVCbGNOx]/) { + $qualifier = $3; + if ($extension !~ /[4SsBKRraEehMmIiUDdgVCbGNOxtf]/ || + ($extension eq "f" && + defined $qualifier && $qualifier !~ /^w/) || + ($extension eq "4" && + defined $qualifier && $qualifier !~ /^cc/)) { $bad_specifier = $specifier; last; } @@ -5850,7 +6771,6 @@ sub process { my $ext_type = "Invalid"; my $use = ""; if ($bad_specifier =~ /p[Ff]/) { - $ext_type = "Deprecated"; $use = " - use %pS instead"; $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); } @@ -5862,7 +6782,7 @@ sub process { } # Check for misused memsets - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { @@ -5880,7 +6800,7 @@ sub process { } # Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) -# if ($^V && $^V ge 5.10.0 && +# if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # if (WARN("PREFER_ETHER_ADDR_COPY", @@ -5891,7 +6811,7 @@ sub process { # } # Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) -# if ($^V && $^V ge 5.10.0 && +# if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # WARN("PREFER_ETHER_ADDR_EQUAL", @@ -5900,7 +6820,7 @@ sub process { # check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr # check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr -# if ($^V && $^V ge 5.10.0 && +# if ($perl_version_ok && # defined $stat && # $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { # @@ -5921,8 +6841,14 @@ sub process { # } # } +# strlcpy uses that should likely be strscpy + if ($line =~ /\bstrlcpy\s*\(/) { + WARN("STRLCPY", + "Prefer strscpy over strlcpy - see: https://lore.kernel.org/r/CAHk-=wgfRnXz0W3D37d01q3JFkr_i_uTL=V6A6G1oUZcprmknw\@mail.gmail.com/\n" . $herecurr); + } + # typecasts on min/max could be min_t/max_t - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { if (defined $2 || defined $7) { @@ -5946,23 +6872,23 @@ sub process { } # check usleep_range arguments - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { my $min = $1; my $max = $7; if ($min eq $max) { WARN("USLEEP_RANGE", - "usleep_range should not use min == max args; see Documentation/timers/timers-howto.txt\n" . "$here\n$stat\n"); + "usleep_range should not use min == max args; see Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && $min > $max) { WARN("USLEEP_RANGE", - "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.txt\n" . "$here\n$stat\n"); + "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); } } # check for naked sscanf - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $line =~ /\bsscanf\b/ && ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && @@ -5976,7 +6902,7 @@ sub process { } # check for simple sscanf that should be kstrto<foo> - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $line =~ /\bsscanf\b/) { my $lc = $stat =~ tr@\n@@; @@ -6014,8 +6940,7 @@ sub process { if (defined $cond) { substr($s, 0, length($cond), ''); } - if ($s =~ /^\s*;/ && - $function_name ne 'uninitialized_var') + if ($s =~ /^\s*;/) { WARN("AVOID_EXTERNS", "externs should be avoided in .c files\n" . $herecurr); @@ -6048,7 +6973,7 @@ sub process { } # check for function definitions - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { $context_function = $1; @@ -6076,26 +7001,26 @@ sub process { if (!grep(/$name/, @setup_docs)) { CHK("UNDOCUMENTED_SETUP", - "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.rst\n" . $herecurr); + "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.txt\n" . $herecurr); } } -# check for pointless casting of kmalloc return - if ($line =~ /\*\s*\)\s*[kv][czm]alloc(_node){0,1}\b/) { +# check for pointless casting of alloc functions + if ($line =~ /\*\s*\)\s*$allocFunctions\b/) { WARN("UNNECESSARY_CASTS", "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); } # alloc style # p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) - if ($^V && $^V ge 5.10.0 && - $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*([kv][mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k|v)[mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { CHK("ALLOC_SIZEOF_STRUCT", "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); } # check for k[mz]alloc with multiplies that could be kmalloc_array/kcalloc - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { my $oldfunc = $3; @@ -6124,14 +7049,15 @@ sub process { } # check for krealloc arg reuse - if ($^V && $^V ge 5.10.0 && - $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*\1\s*,/) { + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && + $1 eq $3) { WARN("KREALLOC_ARG_REUSE", "Reusing the krealloc arg is almost always a bug\n" . $herecurr); } # check for alloc argument mismatch - if ($line =~ /\b(kcalloc|kmalloc_array)\s*\(\s*sizeof\b/) { + if ($line =~ /\b((?:devm_)?(?:kcalloc|kmalloc_array))\s*\(\s*sizeof\b/) { WARN("ALLOC_ARRAY_ARGS", "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); } @@ -6157,43 +7083,46 @@ sub process { } } +# check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) + if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { + WARN("IS_ENABLED_CONFIG", + "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); + } + # check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE - if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(CONFIG_[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { + if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(${CONFIG_}[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { my $config = $1; if (WARN("PREFER_IS_ENABLED", - "Prefer IS_ENABLED(<FOO>) to CONFIG_<FOO> || CONFIG_<FOO>_MODULE\n" . $herecurr) && + "Prefer IS_ENABLED(<FOO>) to ${CONFIG_}<FOO> || ${CONFIG_}<FOO>_MODULE\n" . $herecurr) && $fix) { $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; } } -# check for case / default statements not preceded by break/fallthrough/switch - if ($line =~ /^.\s*(?:case\s+(?:$Ident|$Constant)\s*|default):/) { - my $has_break = 0; - my $has_statement = 0; - my $count = 0; - my $prevline = $linenr; - while ($prevline > 1 && ($file || $count < 3) && !$has_break) { - $prevline--; - my $rline = $rawlines[$prevline - 1]; - my $fline = $lines[$prevline - 1]; - last if ($fline =~ /^\@\@/); - next if ($fline =~ /^\-/); - next if ($fline =~ /^.(?:\s*(?:case\s+(?:$Ident|$Constant)[\s$;]*|default):[\s$;]*)*$/); - $has_break = 1 if ($rline =~ /fall[\s_-]*(through|thru)/i); - next if ($fline =~ /^.[\s$;]*$/); - $has_statement = 1; - $count++; - $has_break = 1 if ($fline =~ /\bswitch\b|\b(?:break\s*;[\s$;]*$|exit\s*\(\b|return\b|goto\b|continue\b)/); - } - if (!$has_break && $has_statement) { - WARN("MISSING_BREAK", - "Possible switch case/default not preceded by break or fallthrough comment\n" . $herecurr); +# check for /* fallthrough */ like comment, prefer fallthrough; + my @fallthroughs = ( + 'fallthrough', + '@fallthrough@', + 'lint -fallthrough[ \t]*', + 'intentional(?:ly)?[ \t]*fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)', + '(?:else,?\s*)?FALL(?:S | |-)?THR(?:OUGH|U|EW)[ \t.!]*(?:-[^\n\r]*)?', + 'Fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + 'fall(?:s | |-)?thr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + ); + if ($raw_comment ne '') { + foreach my $ft (@fallthroughs) { + if ($raw_comment =~ /$ft/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("PREFER_FALLTHROUGH", + "Prefer 'fallthrough;' over fallthrough comment\n" . $herecurr); + last; + } } } # check for switch/default statements without a break; - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { my $cnt = statement_rawlines($stat); @@ -6251,12 +7180,6 @@ sub process { } } -# check for bool bitfields - if ($sline =~ /^.\s+bool\s*$Ident\s*:\s*\d+\s*;/) { - WARN("BOOL_BITFIELD", - "Avoid using bool as bitfield. Prefer bool bitfields as unsigned int or u<8|16|32>\n" . $herecurr); - } - # check for semaphores initialized locked if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { WARN("CONSIDER_COMPLETION", @@ -6275,9 +7198,24 @@ sub process { "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); } +# check for spin_is_locked(), suggest lockdep instead + if ($line =~ /\bspin_is_locked\(/) { + WARN("USE_LOCKDEP", + "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); + } + +# check for deprecated apis + if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { + my $deprecated_api = $1; + my $new_api = $deprecated_apis{$deprecated_api}; + WARN("DEPRECATED_API", + "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); + } + # check for various structs that are normally const (ops, kgdb, device_tree) # and avoid what seem like struct definitions 'struct foo {' - if ($line !~ /\bconst\b/ && + if (defined($const_structs) && + $line !~ /\bconst\b/ && $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { WARN("CONST_STRUCT", "struct $1 should normally be const\n" . $herecurr); @@ -6285,12 +7223,14 @@ sub process { # use of NR_CPUS is usually wrong # ignore definitions of NR_CPUS and usage to define arrays as likely right +# ignore designated initializers using NR_CPUS if ($line =~ /\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && - $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/) + $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/ && + $line !~ /^.\s*\.\w+\s*=\s*.*\bNR_CPUS\b/) { WARN("NR_CPUS", "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); @@ -6303,12 +7243,29 @@ sub process { } # likely/unlikely comparisons similar to "(likely(foo) > 0)" - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { WARN("LIKELY_MISUSE", "Using $1 should generally have parentheses around the comparison\n" . $herecurr); } +# return sysfs_emit(foo, fmt, ...) fmt without newline + if ($line =~ /\breturn\s+sysfs_emit\s*\(\s*$FuncArg\s*,\s*($String)/ && + substr($rawline, $-[6], $+[6] - $-[6]) !~ /\\n"$/) { + my $offset = $+[6] - 1; + if (WARN("SYSFS_EMIT", + "return sysfs_emit(...) formats should include a terminating newline\n" . $herecurr) && + $fix) { + substr($fixed[$fixlinenr], $offset, 0) = '\\n'; + } + } + +# nested likely/unlikely calls + if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { + WARN("LIKELY_MISUSE", + "nested (un)?likely() calls, $1 already uses unlikely() internally\n" . $herecurr); + } + # whine mightly about in_atomic if ($line =~ /\bin_atomic\s*\(/) { if ($realfile =~ m@^drivers/@) { @@ -6320,12 +7277,6 @@ sub process { } } -# check for mutex_trylock_recursive usage - if ($line =~ /mutex_trylock_recursive/) { - ERROR("LOCKING", - "recursive locking is bad, do not use this ever.\n" . $herecurr); - } - # check for lockdep_set_novalidate_class if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || $line =~ /__lockdep_no_validate__\s*\)/ ) { @@ -6346,7 +7297,7 @@ sub process { # check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> # and whether or not function naming is typical and if # DEVICE_ATTR permissions uses are unusual too - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { my $var = $1; @@ -6406,7 +7357,7 @@ sub process { # specific definition of not visible in sysfs. # o Ignore proc_create*(...) uses with a decimal 0 permission as that means # use the default permissions - if ($^V && $^V ge 5.10.0 && + if ($perl_version_ok && defined $stat && $line =~ /$mode_perms_search/) { foreach my $entry (@mode_permission_funcs) { @@ -6468,6 +7419,12 @@ sub process { "unknown module license " . $extracted_string . "\n" . $herecurr); } } + +# check for sysctl duplicate constants + if ($line =~ /\.extra[12]\s*=\s*&(zero|one|int_max)\b/) { + WARN("DUPLICATED_SYSCTL_CONST", + "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); + } } # If we have no input at all, then there is nothing to report on @@ -6482,7 +7439,7 @@ sub process { exit(0); } - # This is not a patch, and we are are in 'no-patch' mode so + # This is not a patch, and we are in 'no-patch' mode so # just keep quiet. if (!$chk_patch && !$is_patch) { exit(0); @@ -6492,9 +7449,38 @@ sub process { ERROR("NOT_UNIFIED_DIFF", "Does not appear to be a unified-diff format patch\n"); } - if ($is_patch && $has_commit_log && $chk_signoff && $signoff == 0) { - ERROR("MISSING_SIGN_OFF", - "Missing Signed-off-by: line(s)\n"); + if ($is_patch && $has_commit_log && $chk_signoff) { + if ($signoff == 0) { + ERROR("MISSING_SIGN_OFF", + "Missing Signed-off-by: line(s)\n"); + } elsif ($authorsignoff != 1) { + # authorsignoff values: + # 0 -> missing sign off + # 1 -> sign off identical + # 2 -> names and addresses match, comments mismatch + # 3 -> addresses match, names different + # 4 -> names match, addresses different + # 5 -> names match, addresses excluding subaddress details (refer RFC 5233) match + + my $sob_msg = "'From: $author' != 'Signed-off-by: $author_sob'"; + + if ($authorsignoff == 0) { + ERROR("NO_AUTHOR_SIGN_OFF", + "Missing Signed-off-by: line by nominal patch author '$author'\n"); + } elsif ($authorsignoff == 2) { + CHK("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email comments mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 3) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email name mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 4) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email address mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 5) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email subaddress mismatch: $sob_msg\n"); + } + } } print report_dump(); diff --git a/tools/checkpatch.pl-update b/tools/checkpatch.pl-update index 2462038..58d1f40 100755 --- a/tools/checkpatch.pl-update +++ b/tools/checkpatch.pl-update @@ -66,6 +66,10 @@ download() { # Then any data it uses. url="${CGIT_URL}/scripts/spelling.txt?h=v${version}" wget "${url}" -O spelling.txt + + # Then any data it uses. + url="${CGIT_URL}/scripts/const_structs.checkpatch?h=v${version}" + wget "${url}" -O const_structs.checkpatch } main() { diff --git a/tools/clang-format.py b/tools/clang-format.py index 2533b15..bb233e6 100755 --- a/tools/clang-format.py +++ b/tools/clang-format.py @@ -75,14 +75,14 @@ def main(argv): if opts.extensions: cmd.extend(['--extensions', opts.extensions]) if not opts.working_tree: - cmd.extend(['%s^' % opts.commit, opts.commit]) + cmd.extend([f'{opts.commit}^', opts.commit]) cmd.extend(['--'] + opts.files) # Fail gracefully if clang-format itself aborts/fails. try: result = rh.utils.run(cmd, capture_output=True) except rh.utils.CalledProcessError as e: - print('clang-format failed:\n%s' % (e,), file=sys.stderr) + print(f'clang-format failed:\n{e}', file=sys.stderr) print('\nPlease report this to the clang team.', file=sys.stderr) return 1 @@ -110,9 +110,9 @@ def main(argv): else: print('The following files have formatting errors:') for filename in diff_filenames: - print('\t%s' % filename) - print('You can try to fix this by running:\n%s --fix %s' % - (sys.argv[0], rh.shell.cmd_to_str(argv))) + print(f'\t{filename}') + print('You can try to fix this by running:\n' + '{sys.argv[0]} --fix {rh.shell.cmd_to_str(argv)}') return 1 return 0 diff --git a/tools/const_structs.checkpatch b/tools/const_structs.checkpatch new file mode 100644 index 0000000..1eeb7b4 --- /dev/null +++ b/tools/const_structs.checkpatch @@ -0,0 +1,95 @@ +acpi_dock_ops +address_space_operations +backlight_ops +block_device_operations +clk_ops +comedi_lrange +component_ops +dentry_operations +dev_pm_ops +dma_map_ops +driver_info +drm_connector_funcs +drm_encoder_funcs +drm_encoder_helper_funcs +dvb_frontend_ops +dvb_tuner_ops +ethtool_ops +extent_io_ops +fb_ops +file_lock_operations +file_operations +hv_ops +hwmon_ops +ib_device_ops +ide_dma_ops +ide_port_ops +ieee80211_ops +iio_buffer_setup_ops +inode_operations +intel_dvo_dev_ops +irq_domain_ops +item_operations +iwl_cfg +iwl_ops +kernel_param_ops +kgdb_arch +kgdb_io +kset_uevent_ops +lock_manager_operations +machine_desc +microcode_ops +mlxsw_reg_info +mtd_ooblayout_ops +mtrr_ops +nand_controller_ops +neigh_ops +net_device_ops +nft_expr_ops +nlmsvc_binding +nvkm_device_chip +of_device_id +pci_raw_ops +phy_ops +pinconf_ops +pinctrl_ops +pinmux_ops +pipe_buf_operations +platform_hibernation_ops +platform_suspend_ops +proc_ops +proto_ops +pwm_ops +regmap_access_table +regulator_ops +reset_control_ops +rpc_pipe_ops +rtc_class_ops +sd_desc +sdhci_ops +seq_operations +sirfsoc_padmux +snd_ac97_build_ops +snd_pcm_ops +snd_rawmidi_ops +snd_soc_component_driver +snd_soc_dai_ops +snd_soc_ops +soc_pcmcia_socket_ops +stacktrace_ops +sysfs_ops +tty_operations +uart_ops +usb_mon_operations +v4l2_ctrl_ops +v4l2_ioctl_ops +v4l2_subdev_core_ops +v4l2_subdev_internal_ops +v4l2_subdev_ops +v4l2_subdev_pad_ops +v4l2_subdev_video_ops +vb2_ops +vm_operations_struct +wacom_features +watchdog_ops +wd_ops diff --git a/tools/google-java-format.py b/tools/google-java-format.py index 6659511..829b8e3 100755 --- a/tools/google-java-format.py +++ b/tools/google-java-format.py @@ -66,19 +66,17 @@ def main(argv): # https://github.com/google/google-java-format/issues/108 format_path = find_executable(opts.google_java_format) if not format_path: - print('Unable to find google-java-format at %s' % - opts.google_java_format) + print('Unable to find google-java-format at: {opts.google_java_format}', + file=sys.stderr) return 1 extra_env = { - 'PATH': '%s%s%s' % (os.path.dirname(format_path), - os.pathsep, - os.environ['PATH']) + 'PATH': os.path.dirname(format_path) + os.pathsep + os.environ['PATH'], } # TODO: Delegate to the tool once this issue is resolved: # https://github.com/google/google-java-format/issues/107 - diff_cmd = ['git', 'diff', '--no-ext-diff', '-U0', '%s^!' % opts.commit] + diff_cmd = ['git', 'diff', '--no-ext-diff', '-U0', f'{opts.commit}^!'] diff_cmd.extend(['--'] + opts.files) diff = rh.utils.run(diff_cmd, capture_output=True).stdout @@ -92,8 +90,7 @@ def main(argv): extra_env=extra_env).stdout if stdout: print('One or more files in your commit have Java formatting errors.') - print('You can run `%s --fix %s` to fix this' % - (sys.argv[0], rh.shell.cmd_to_str(argv))) + print('You can run: {sys.argv[0]} --fix {rh.shell.cmd_to_str(argv)}') return 1 return 0 diff --git a/tools/pylint.py b/tools/pylint.py index 570f055..3fbb148 100755 --- a/tools/pylint.py +++ b/tools/pylint.py @@ -24,7 +24,7 @@ import subprocess assert (sys.version_info.major, sys.version_info.minor) >= (3, 6), ( - 'Python 3.6 or newer is required; found %s' % (sys.version,)) + f'Python 3.6 or newer is required; found {sys.version}') DEFAULT_PYLINTRC_PATH = os.path.join( @@ -37,8 +37,8 @@ def is_pylint3(pylint): result = subprocess.run([pylint, '--version'], stdout=subprocess.PIPE, check=True) if b'Python 3' not in result.stdout: - print('%s: unable to locate a Python 3 version of pylint; Python 3 ' - 'support cannot be guaranteed' % (__file__,), file=sys.stderr) + print(f'{__file__}: unable to locate a Python 3 version of pylint; ' + 'Python 3 support cannot be guaranteed', file=sys.stderr) return False return True @@ -55,8 +55,8 @@ def find_pylint3(): # If there's no pylint, give up. if not shutil.which('pylint'): - print('%s: unable to locate pylint; please install:\n' - 'sudo apt-get install pylint' % (__file__,), file=sys.stderr) + print(f'{__file__}: unable to locate pylint; please install:\n' + 'sudo apt-get install pylint', file=sys.stderr) sys.exit(1) return 'pylint' @@ -103,7 +103,7 @@ def main(argv): pylintrc = DEFAULT_PYLINTRC_PATH # If we pass a non-existent rcfile to pylint, it'll happily ignore # it. - assert os.path.exists(pylintrc), 'Could not find %s' % pylintrc + assert os.path.exists(pylintrc), f'Could not find {pylintrc}' cmd += ['--rcfile', pylintrc] cmd += unknown + opts.files @@ -116,10 +116,10 @@ def main(argv): return 0 except OSError as e: if e.errno == errno.ENOENT: - print('%s: unable to run `%s`: %s' % (__file__, cmd[0], e), + print(f'{__file__}: unable to run `{cmd[0]}`: {e}', file=sys.stderr) - print('%s: Try installing pylint: sudo apt-get install %s' % - (__file__, os.path.basename(cmd[0])), file=sys.stderr) + print(f'{__file__}: Try installing pylint: sudo apt-get install ' + f'{os.path.basename(cmd[0])}', file=sys.stderr) return 1 raise diff --git a/tools/spelling.txt b/tools/spelling.txt index 9a058cf..0c8b79c 100644 --- a/tools/spelling.txt +++ b/tools/spelling.txt @@ -9,7 +9,12 @@ # abandonning||abandoning abigious||ambiguous +abitrary||arbitrary abitrate||arbitrate +abnornally||abnormally +abnrormal||abnormal +abord||abort +aboslute||absolute abov||above abreviated||abbreviated absense||absence @@ -17,6 +22,7 @@ absolut||absolute absoulte||absolute acccess||access acceess||access +accelaration||acceleration acceleratoin||acceleration accelleration||acceleration accesing||accessing @@ -25,6 +31,7 @@ accessable||accessible accesss||access accidentaly||accidentally accidentually||accidentally +acclerated||accelerated accoding||according accomodate||accommodate accomodates||accommodates @@ -34,8 +41,11 @@ accout||account accquire||acquire accquired||acquired accross||across +accumalate||accumulate +accumalator||accumulator acessable||accessible acess||access +acessing||accessing achitecture||architecture acient||ancient acitions||actions @@ -49,7 +59,9 @@ activete||activate actived||activated actualy||actually acumulating||accumulating +acumulative||accumulative acumulator||accumulator +acutally||actually adapater||adapter addional||additional additionaly||additionally @@ -58,18 +70,22 @@ addres||address adddress||address addreses||addresses addresss||address +addrress||address aditional||additional aditionally||additionally aditionaly||additionally adminstrative||administrative adress||address adresses||addresses +adrresses||addresses +advertisment||advertisement adviced||advised afecting||affecting againt||against agaist||against aggreataon||aggregation aggreation||aggregation +ajust||adjust albumns||albums alegorical||allegorical algined||aligned @@ -77,6 +93,7 @@ algorith||algorithm algorithmical||algorithmically algoritm||algorithm algoritms||algorithms +algorithmn||algorithm algorrithm||algorithm algorritm||algorithm aligment||alignment @@ -88,6 +105,7 @@ alloated||allocated allocatote||allocate allocatrd||allocated allocte||allocate +allocted||allocated allpication||application alocate||allocate alogirhtms||algorithms @@ -95,11 +113,16 @@ alogrithm||algorithm alot||a lot alow||allow alows||allows +alreay||already +alredy||already altough||although alue||value ambigious||ambiguous +ambigous||ambiguous amoung||among amout||amount +amplifer||amplifier +amplifyer||amplifier an union||a union an user||a user an userspace||a userspace @@ -130,6 +153,7 @@ arbitary||arbitrary architechture||architecture arguement||argument arguements||arguments +arithmatic||arithmetic aritmetic||arithmetic arne't||aren't arraival||arrival @@ -138,27 +162,43 @@ artillary||artillery asign||assign asser||assert assertation||assertion +assertting||asserting +assgined||assigned assiged||assigned assigment||assignment assigments||assignments assistent||assistant +assocaited||associated +assocating||associating assocation||association associcated||associated assotiated||associated +asssert||assert assum||assume assumtpion||assumption asuming||assuming asycronous||asynchronous +asychronous||asynchronous asynchnous||asynchronous +asynchromous||asynchronous +asymetric||asymmetric +asymmeric||asymmetric +atleast||at least atomatically||automatically atomicly||atomically atempt||attempt +atrributes||attributes attachement||attachment +attatch||attach attched||attached +attemp||attempt attemps||attempts attemping||attempting +attepmpt||attempt +attnetion||attention attruibutes||attributes authentification||authentication +authenicated||authenticated automaticaly||automatically automaticly||automatically automatize||automate @@ -172,6 +212,7 @@ avaible||available availabe||available availabled||available availablity||availability +availaible||available availale||available availavility||availability availble||available @@ -201,32 +242,50 @@ beter||better betweeen||between bianries||binaries bitmast||bitmask +bitwiedh||bitwidth boardcast||broadcast borad||board boundry||boundary brievely||briefly +brigde||bridge +broadcase||broadcast broadcat||broadcast +bufer||buffer +bufufer||buffer cacluated||calculated +caculate||calculate caculation||calculation +cadidate||candidate +cahces||caches calender||calendar calescing||coalescing calle||called callibration||calibration +callled||called +callser||caller calucate||calculate calulate||calculate cancelation||cancellation cancle||cancel +cant||can't +cant'||can't +canot||cannot +cann't||can't capabilites||capabilities +capabilties||capabilities capabilty||capability capabitilies||capabilities +capablity||capability capatibilities||capabilities capapbilities||capabilities +caputure||capture carefuly||carefully cariage||carriage catagory||category cehck||check challange||challenge challanges||challenges +chache||cache chanell||channel changable||changeable chanined||chained @@ -240,6 +299,7 @@ charaters||characters charcter||character chcek||check chck||check +checksumed||checksummed checksuming||checksumming childern||children childs||children @@ -255,7 +315,9 @@ claread||cleared clared||cleared closeing||closing clustred||clustered +cnfiguration||configuration coexistance||coexistence +colescing||coalescing collapsable||collapsible colorfull||colorful comand||command @@ -266,14 +328,17 @@ comminucation||communication commited||committed commiting||committing committ||commit +commnunication||communication commoditiy||commodity comsume||consume comsumer||consumer comsuming||consuming compability||compatibility compaibility||compatibility +comparsion||comparison compatability||compatibility compatable||compatible +compatibililty||compatibility compatibiliy||compatibility compatibilty||compatibility compatiblity||compatibility @@ -285,22 +350,32 @@ completly||completely complient||compliant componnents||components compoment||component +comppatible||compatible compres||compress compresion||compression comression||compression +comunicate||communicate comunication||communication conbination||combination conditionaly||conditionally +conditon||condition +condtion||condition conected||connected -connecetd||connected +conector||connector +configration||configuration +configred||configured configuartion||configuration +configuation||configuration +configued||configured configuratoin||configuration configuraton||configuration configuretion||configuration configutation||configuration conider||consider conjuction||conjunction +connecetd||connected connectinos||connections +connetor||connector connnection||connection connnections||connections consistancy||consistency @@ -310,11 +385,13 @@ containts||contains contaisn||contains contant||contact contence||contents +contiguos||contiguous continious||continuous continous||continuous continously||continuously continueing||continuing contraints||constraints +contruct||construct contol||control contoller||controller controled||controlled @@ -340,15 +417,23 @@ cunter||counter curently||currently cylic||cyclic dafault||default +deactive||deactivate deafult||default deamon||daemon +debouce||debounce +decendant||descendant +decendants||descendants decompres||decompress +decsribed||described decription||description dectected||detected defailt||default +deferal||deferral +deffered||deferred defferred||deferred definate||definite definately||definitely +definiation||definition defintion||definition defintions||definitions defualt||default @@ -362,29 +447,35 @@ delare||declare delares||declares delaring||declaring delemiter||delimiter +delievered||delivered demodualtor||demodulator demension||dimension dependancies||dependencies dependancy||dependency dependant||dependent +dependend||dependent depreacted||deprecated depreacte||deprecate desactivate||deactivate desciptor||descriptor desciptors||descriptors +descripto||descriptor descripton||description descrition||description descritptor||descriptor desctiptor||descriptor desriptor||descriptor desriptors||descriptors +desination||destination destionation||destination +destoried||destroyed destory||destroy destoryed||destroyed destorys||destroys destroied||destroyed detabase||database deteced||detected +detectt||detect develope||develop developement||development developped||developed @@ -394,44 +485,75 @@ developpment||development deveolpment||development devided||divided deviece||device +devision||division diable||disable +diabled||disabled +dicline||decline dictionnary||dictionary didnt||didn't diferent||different differrence||difference diffrent||different +differenciate||differentiate diffrentiate||differentiate difinition||definition +digial||digital +dimention||dimension dimesions||dimensions +diconnected||disconnected +disabed||disabled +disble||disable +disgest||digest +disired||desired +dispalying||displaying +dissable||disable diplay||display +directon||direction +direcly||directly direectly||directly +diregard||disregard disassocation||disassociation disapear||disappear disapeared||disappeared disappared||disappeared +disbale||disable +disbaled||disabled disble||disable disbled||disabled disconnet||disconnect discontinous||discontinuous +disharge||discharge +disnabled||disabled dispertion||dispersion dissapears||disappears +dissconect||disconnect distiction||distinction +divisable||divisible +divsiors||divisors docuentation||documentation documantation||documentation documentaion||documentation documment||document doesnt||doesn't +donwload||download +donwloading||downloading dorp||drop dosen||doesn downlad||download downlads||downloads +droped||dropped +droput||dropout druing||during +dyanmic||dynamic dynmaic||dynamic +eanable||enable +eanble||enable easilly||easily ecspecially||especially edditable||editable editting||editing efective||effective +effectivness||effectiveness efficently||efficiently ehther||ether eigth||eight @@ -439,16 +561,23 @@ elementry||elementary eletronic||electronic embeded||embedded enabledi||enabled +enbale||enable +enble||enable enchanced||enhanced encorporating||incorporating encrupted||encrypted encrypiton||encryption encryptio||encryption endianess||endianness +enpoint||endpoint enhaced||enhanced enlightnment||enlightenment +enqueing||enqueuing +entires||entries +entites||entities entrys||entries enocded||encoded +enought||enough enterily||entirely enviroiment||environment enviroment||environment @@ -460,13 +589,24 @@ equivelant||equivalent equivilant||equivalent eror||error errorr||error +errror||error estbalishment||establishment etsablishment||establishment etsbalishment||establishment +evalute||evaluate +evalutes||evaluates +evalution||evaluation excecutable||executable exceded||exceeded +exceds||exceeds +exceeed||exceed excellant||excellent +exchnage||exchange +execeeded||exceeded +execeeds||exceeds exeed||exceed +exeeds||exceeds +exeuction||execution existance||existence existant||existent exixt||exist @@ -474,6 +614,7 @@ exlcude||exclude exlcusive||exclusive exmaple||example expecially||especially +experies||expires explicite||explicit explicitely||explicitly explict||explicit @@ -482,11 +623,16 @@ explictly||explicitly expresion||expression exprimental||experimental extened||extended +exteneded||extended extensability||extensibility extention||extension +extenstion||extension extracter||extractor -falied||failed +faied||failed +faield||failed faild||failed +failded||failed +failer||failure faill||fail failied||failed faillure||failure @@ -504,8 +650,12 @@ feautures||features fetaure||feature fetaures||features fileystem||filesystem +fimrware||firmware fimware||firmware +firmare||firmware +firmaware||firmware firware||firmware +firwmare||firmware finanize||finalize findn||find finilizes||finalizes @@ -520,13 +670,18 @@ forseeable||foreseeable forse||force fortan||fortran forwardig||forwarding +frambuffer||framebuffer framming||framing framwork||framework +frequence||frequency frequncy||frequency +frequancy||frequency frome||from fucntion||function fuction||function fuctions||functions +fullill||fulfill +funcation||function funcion||function functionallity||functionality functionaly||functionally @@ -537,14 +692,19 @@ funtions||functions furthur||further futhermore||furthermore futrue||future +gatable||gateable +gateing||gating +gauage||gauge gaurenteed||guaranteed generiously||generously genereate||generate +genereted||generated genric||generic globel||global grabing||grabbing grahical||graphical grahpical||graphical +granularty||granularity grapic||graphic grranted||granted guage||gauge @@ -553,14 +713,22 @@ guarentee||guarantee halfs||halves hander||handler handfull||handful +hanlde||handle hanled||handled happend||happened +hardare||hardware harware||hardware +havind||having heirarchically||hierarchically +heirarchy||hierarchy helpfull||helpful +hearbeat||heartbeat +heterogenous||heterogeneous +hexdecimal||hexadecimal hybernate||hibernate hierachy||hierarchy hierarchie||hierarchy +homogenous||homogeneous howver||however hsould||should hypervior||hypervisor @@ -568,12 +736,16 @@ hypter||hyper identidier||identifier iligal||illegal illigal||illegal +illgal||illegal +iomaped||iomapped imblance||imbalance immeadiately||immediately immedaite||immediate +immedate||immediate immediatelly||immediately immediatly||immediately immidiate||immediate +immutible||immutable impelentation||implementation impementated||implemented implemantation||implementation @@ -591,10 +763,13 @@ incative||inactive incomming||incoming incompatabilities||incompatibilities incompatable||incompatible +incompatble||incompatible inconsistant||inconsistent increas||increase incremeted||incremented incrment||increment +incuding||including +inculde||include indendation||indentation indended||intended independant||independent @@ -603,6 +778,8 @@ independed||independent indiate||indicate indicat||indicate inexpect||inexpected +inferface||interface +infinit||infinite infomation||information informatiom||information informations||information @@ -617,14 +794,24 @@ initalize||initialize initation||initiation initators||initiators initialiazation||initialization +initializationg||initialization initializiation||initialization +initialze||initialize initialzed||initialized +initialzing||initializing initilization||initialization initilize||initialize +initliaze||initialize +initilized||initialized inofficial||unofficial +inrerface||interface insititute||institute +instace||instance instal||install +instanciate||instantiate instanciated||instantiated +instuments||instruments +insufficent||insufficient inteface||interface integreated||integrated integrety||integrity @@ -635,17 +822,20 @@ interanl||internal interchangable||interchangeable interferring||interfering interger||integer +intergrated||integrated intermittant||intermittent internel||internal interoprability||interoperability interuupt||interrupt +interupt||interrupt +interupts||interrupts interrface||interface interrrupt||interrupt interrup||interrupt interrups||interrupts interruptted||interrupted interupted||interrupted -interupt||interrupt +intiailized||initialized intial||initial intialisation||initialisation intialised||initialised @@ -654,10 +844,14 @@ intialization||initialization intialized||initialized intialize||initialize intregral||integral +intrerrupt||interrupt intrrupt||interrupt intterrupt||interrupt intuative||intuitive +inavlid||invalid invaid||invalid +invaild||invalid +invailid||invalid invald||invalid invalde||invalid invalide||invalid @@ -666,14 +860,18 @@ invalud||invalid invididual||individual invokation||invocation invokations||invocations +ireelevant||irrelevant irrelevent||irrelevant isnt||isn't isssue||issue +issus||issues +iteraions||iterations iternations||iterations itertation||iteration itslef||itself jave||java jeffies||jiffies +jumpimng||jumping juse||just jus||just kown||known @@ -683,6 +881,7 @@ langauge||language langugage||language lauch||launch layed||laid +legnth||length leightweight||lightweight lengh||length lenght||length @@ -693,29 +892,45 @@ libary||library librairies||libraries libraris||libraries licenceing||licencing +limted||limited +logaritmic||logarithmic loggging||logging loggin||login logile||logfile +loobpack||loopback loosing||losing losted||lost +maangement||management machinary||machinery +maibox||mailbox maintainance||maintenance maintainence||maintenance maintan||maintain makeing||making +mailformed||malformed malplaced||misplaced malplace||misplace managable||manageable +managament||management managment||management mangement||management +manger||manager manoeuvering||maneuvering +manufaucturing||manufacturing mappping||mapping +maping||mapping +matchs||matches mathimatical||mathematical mathimatic||mathematic mathimatics||mathematics +maximium||maximum maxium||maximum mechamism||mechanism meetign||meeting +memeory||memory +memmber||member +memoery||memory +memroy||memory ment||meant mergable||mergeable mesage||message @@ -723,11 +938,15 @@ messags||messages messgaes||messages messsage||message messsages||messages +metdata||metadata micropone||microphone microprocesspr||microprocessor +migrateable||migratable milliseonds||milliseconds minium||minimum minimam||minimum +minimun||minimum +miniumum||minimum minumum||minimum misalinged||misaligned miscelleneous||miscellaneous @@ -736,21 +955,29 @@ mispelled||misspelled mispelt||misspelt mising||missing mismactch||mismatch +missign||missing missmanaged||mismanaged missmatch||mismatch +misssing||missing miximum||maximum mmnemonic||mnemonic mnay||many +modfiy||modify +modifer||modifier +modul||module modulues||modules momery||memory memomry||memory +monitring||monitoring monochorome||monochrome monochromo||monochrome monocrome||monochrome mopdule||module mroe||more +multipler||multiplier mulitplied||multiplied multidimensionnal||multidimensional +multipe||multiple multple||multiple mumber||number muticast||multicast @@ -772,21 +999,30 @@ nerver||never nescessary||necessary nessessary||necessary noticable||noticeable +notication||notification notications||notifications +notifcations||notifications notifed||notified +notity||notify +nubmer||number numebr||number numner||number obtaion||obtain +obusing||abusing occassionally||occasionally occationally||occasionally occurance||occurrence occurances||occurrences +occurd||occurred occured||occurred occurence||occurrence occure||occurred -occured||occurred occuring||occurring +offser||offset offet||offset +offlaod||offload +offloded||offloaded +offseting||offsetting omited||omitted omiting||omitting omitt||omit @@ -794,22 +1030,29 @@ ommiting||omitting ommitted||omitted onself||oneself ony||only +openning||opening operatione||operation opertaions||operations +opportunies||opportunities optionnal||optional optmizations||optimizations orientatied||orientated orientied||oriented orignal||original +originial||original otherise||otherwise ouput||output oustanding||outstanding overaall||overall overhread||overhead overlaping||overlapping +oveflow||overflow +overflw||overflow +overlfow||overflow overide||override overrided||overridden overriden||overridden +overrrun||overrun overun||overrun overwritting||overwriting overwriten||overwritten @@ -820,6 +1063,7 @@ packege||package packge||package packtes||packets pakage||package +paket||packet pallette||palette paln||plan paramameters||parameters @@ -829,23 +1073,34 @@ parametes||parameters parametised||parametrised paramter||parameter paramters||parameters +parmaters||parameters particuarly||particularly particularily||particularly +partion||partition +partions||partitions partiton||partition pased||passed passin||passing pathes||paths +pattrns||patterns pecularities||peculiarities peformance||performance +peforming||performing peice||piece pendantic||pedantic peprocessor||preprocessor +perfomance||performance perfoming||performing +perfomring||performing +periperal||peripheral +peripherial||peripheral permissons||permissions peroid||period persistance||persistence persistant||persistent +phoneticly||phonetically plalform||platform +platfoem||platform platfrom||platform plattform||platform pleaes||please @@ -857,7 +1112,10 @@ poiter||pointer posible||possible positon||position possibilites||possibilities +potocol||protocol powerfull||powerful +pramater||parameter +preamle||preamble preample||preamble preapre||prepare preceeded||preceded @@ -868,9 +1126,16 @@ precission||precision preemptable||preemptible prefered||preferred prefferably||preferably +prefitler||prefilter +preform||perform premption||preemption prepaired||prepared +prepate||prepare +preperation||preparation +preprare||prepare pressre||pressure +presuambly||presumably +previosuly||previously primative||primitive princliple||principle priorty||priority @@ -878,6 +1143,7 @@ privilaged||privileged privilage||privilege priviledge||privilege priviledges||privileges +privleges||privileges probaly||probably procceed||proceed proccesors||processors @@ -891,12 +1157,17 @@ processsed||processed processsing||processing procteted||protected prodecure||procedure +progamming||programming progams||programs progess||progress +programable||programmable programers||programmers programm||program programms||programs +progres||progress progresss||progress +prohibitted||prohibited +prohibitting||prohibiting promiscous||promiscuous promps||prompts pronnounced||pronounced @@ -906,35 +1177,45 @@ pronunce||pronounce propery||property propigate||propagate propigation||propagation +propogation||propagation propogate||propagate prosess||process protable||portable protcol||protocol protecion||protection +protedcted||protected protocoll||protocol promixity||proximity psudo||pseudo psuedo||pseudo psychadelic||psychedelic +purgable||purgeable pwoer||power +queing||queuing quering||querying +queus||queues randomally||randomly raoming||roaming reasearcher||researcher reasearchers||researchers reasearch||research +receieve||receive recepient||recipient +recevied||received receving||receiving +recievd||received recieved||received recieve||receive reciever||receiver recieves||receives +recieving||receiving recogniced||recognised recognizeable||recognizable recommanded||recommended recyle||recycle redircet||redirect redirectrion||redirection +redundacy||redundancy reename||rename refcounf||refcount refence||reference @@ -944,7 +1225,9 @@ refering||referring refernces||references refernnce||reference refrence||reference +registed||registered registerd||registered +registeration||registration registeresd||registered registerred||registered registes||registers @@ -957,6 +1240,7 @@ regulamentations||regulations reigstration||registration releated||related relevent||relevant +reloade||reload remoote||remote remore||remote removeable||removable @@ -967,25 +1251,38 @@ replys||replies reponse||response representaion||representation reqeust||request +reqister||register +requed||requeued requestied||requested requiere||require requirment||requirement requred||required requried||required requst||request +requsted||requested +reregisteration||reregistration reseting||resetting +reseved||reserved +reseverd||reserved resizeable||resizable resouce||resource resouces||resources resoures||resources responce||response +resrouce||resource ressizes||resizes ressource||resource ressources||resources +restesting||retesting +resumbmitting||resubmitting retransmited||retransmitted retreived||retrieved retreive||retrieve +retreiving||retrieving retrive||retrieve +retrived||retrieved +retrun||return +retun||return retuned||returned reudce||reduce reuest||request @@ -1006,30 +1303,43 @@ sacrifying||sacrificing safly||safely safty||safety savable||saveable +scaleing||scaling scaned||scanned scaning||scanning scarch||search +schdule||schedule seach||search searchs||searches +secion||section secquence||sequence secund||second segement||segment +seleted||selected +semaphone||semaphore +senario||scenario senarios||scenarios sentivite||sensitive separatly||separately sepcify||specify -sepc||spec seperated||separated seperately||separately seperate||separate seperatly||separately seperator||separator sepperate||separate +seqeunce||sequence +seqeuncer||sequencer +seqeuencer||sequencer sequece||sequence +sequemce||sequence sequencial||sequential +serivce||service serveral||several +servive||service setts||sets settting||setting +shapshot||snapshot +shoft||shift shotdown||shutdown shoud||should shouldnt||shouldn't @@ -1037,6 +1347,7 @@ shoule||should shrinked||shrunk siginificantly||significantly signabl||signal +significanly||significantly similary||similarly similiar||similar simlar||similar @@ -1046,15 +1357,22 @@ singaled||signaled singal||signal singed||signed sleeped||slept +sliped||slipped +softwade||software softwares||software +soley||solely +souce||source speach||speech specfic||specific +specfield||specified speciefied||specified specifc||specific specifed||specified specificatin||specification specificaton||specification +specificed||specified specifing||specifying +specifiy||specify specifiying||specifying speficied||specified speicify||specify @@ -1071,8 +1389,12 @@ staion||station standardss||standards standartization||standardization standart||standard +standy||standby +stardard||standard staticly||statically +statuss||status stoped||stopped +stoping||stopping stoppped||stopped straming||streaming struc||struct @@ -1084,13 +1406,17 @@ sturcture||structure subdirectoires||subdirectories suble||subtle substract||subtract +submited||submitted submition||submission +succeded||succeeded +suceed||succeed succesfully||successfully succesful||successful successed||succeeded successfull||successful successfuly||successfully sucessfully||successfully +sucessful||successful sucess||success superflous||superfluous superseeded||superseded @@ -1103,11 +1429,13 @@ supportin||supporting suppoted||supported suppported||supported suppport||support +supprot||support supress||suppress surpressed||suppressed surpresses||suppresses susbsystem||subsystem suspeneded||suspended +suspsend||suspend suspicously||suspiciously swaping||swapping switchs||switches @@ -1119,9 +1447,12 @@ swithcing||switching swithed||switched swithing||switching swtich||switch +syfs||sysfs symetric||symmetric synax||syntax synchonized||synchronized +sychronization||synchronization +synchronuously||synchronously syncronize||synchronize syncronized||synchronized syncronizing||synchronizing @@ -1130,28 +1461,43 @@ syste||system sytem||system sythesis||synthesis taht||that +tansmit||transmit targetted||targeted targetting||targeting +taskelt||tasklet teh||the temorary||temporary temproarily||temporarily +temperture||temperature +thead||thread therfore||therefore thier||their threds||threads +threee||three threshhold||threshold thresold||threshold throught||through +trackling||tracking troughput||throughput +trys||tries thses||these +tiggers||triggers tiggered||triggered tipically||typically +timeing||timing timout||timeout tmis||this +toogle||toggle torerable||tolerable +traget||target +traking||tracking tramsmitted||transmitted tramsmit||transmit tranasction||transaction +tranceiver||transceiver tranfer||transfer +tranmission||transmission +transcevier||transceiver transciever||transceiver transferd||transferred transfered||transferred @@ -1162,6 +1508,8 @@ transormed||transformed trasfer||transfer trasmission||transmission treshold||threshold +triggerd||triggered +trigerred||triggered trigerring||triggering trun||turn tunning||tuning @@ -1169,8 +1517,12 @@ ture||true tyep||type udpate||update uesd||used +uknown||unknown +usccess||success uncommited||uncommitted +uncompatible||incompatible unconditionaly||unconditionally +undeflow||underflow underun||underrun unecessary||unnecessary unexecpted||unexpected @@ -1181,11 +1533,17 @@ unexpeted||unexpected unexpexted||unexpected unfortunatelly||unfortunately unifiy||unify +uniterrupted||uninterrupted +uninterruptable||uninterruptible unintialized||uninitialized +unitialized||uninitialized unkmown||unknown unknonw||unknown +unknouwn||unknown unknow||unknown unkown||unknown +unamed||unnamed +uneeded||unneeded unneded||unneeded unneccecary||unnecessary unneccesary||unnecessary @@ -1194,6 +1552,7 @@ unnecesary||unnecessary unneedingly||unnecessarily unnsupported||unsupported unmached||unmatched +unprecise||imprecise unregester||unregister unresgister||unregister unrgesiter||unregister @@ -1203,13 +1562,18 @@ unsolicitied||unsolicited unsuccessfull||unsuccessful unsuported||unsupported untill||until +ununsed||unused unuseful||useless +unvalid||invalid upate||update +upsupported||unsupported +useable||usable usefule||useful usefull||useful usege||usage usera||users usualy||usually +usupported||unsupported utilites||utilities utillities||utilities utilties||utilities @@ -1224,6 +1588,9 @@ varible||variable varient||variant vaule||value verbse||verbose +veify||verify +verfication||verification +veriosn||version verisons||versions verison||version verson||version @@ -1233,7 +1600,10 @@ virtaul||virtual virtiual||virtual visiters||visitors vitual||virtual +vunerable||vulnerable wakeus||wakeups +was't||wasn't +wathdog||watchdog wating||waiting wiat||wait wether||whether @@ -1247,8 +1617,10 @@ wiil||will wirte||write withing||within wnat||want +wont||won't workarould||workaround writeing||writing writting||writing +wtih||with zombe||zombie zomebie||zombie |