aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-06-27 16:38:18 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-06-27 16:38:18 +0000
commit0498fd5b50e25ab7c65471f382e27c76df8e30c1 (patch)
tree1bb0dd828c050251888c7fb65bfad048521b6348
parent9cf165bb6ef0dfd16e3bc64c4d942efc5ee0952a (diff)
parente0447aa1766a3e1cb73c82c41ff9d411d45b57fb (diff)
downloadrepohooks-androidx-sharetarget-release.tar.gz
Snap for 8755081 from e0447aa1766a3e1cb73c82c41ff9d411d45b57fb to androidx-sharetarget-releaseandroidx-sharetarget-release
Change-Id: I8872fa2d68385e4c6e1cbc01ef82674939799324
-rw-r--r--PREUPLOAD.cfg1
-rw-r--r--README.md9
-rwxr-xr-xpre-upload.py116
-rw-r--r--rh/config.py44
-rwxr-xr-xrh/config_test.py14
-rwxr-xr-xrh/config_unittest.py2
-rw-r--r--rh/git.py50
-rw-r--r--rh/hooks.py146
-rwxr-xr-xrh/hooks_unittest.py92
-rw-r--r--rh/results.py5
-rw-r--r--rh/shell.py16
-rwxr-xr-xrh/shell_unittest.py23
-rw-r--r--rh/terminal.py22
-rw-r--r--rh/utils.py41
-rwxr-xr-xrh/utils_unittest.py14
-rwxr-xr-xtools/android_test_mapping_format.py13
-rwxr-xr-xtools/android_test_mapping_format_unittest.py48
-rwxr-xr-xtools/checkpatch.pl1844
-rwxr-xr-xtools/checkpatch.pl-update4
-rwxr-xr-xtools/clang-format.py22
-rwxr-xr-xtools/clang-format_unittest.py94
-rw-r--r--tools/const_structs.checkpatch95
-rwxr-xr-xtools/google-java-format.py15
-rwxr-xr-xtools/pylint.py18
-rw-r--r--tools/spelling.txt382
25 files changed, 2424 insertions, 706 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 81f505d..631915d 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -5,6 +5,7 @@ hooks_unittest = ./rh/hooks_unittest.py
shell_unittest = ./rh/shell_unittest.py
utils_unittest = ./rh/utils_unittest.py
android_test_mapping_format_unittest = ./tools/android_test_mapping_format_unittest.py
+clang-format unittest = ./tools/clang-format_unittest.py
config_test = ./rh/config_test.py --check-env --commit-id ${PREUPLOAD_COMMIT} --commit-msg ${PREUPLOAD_COMMIT_MESSAGE} --repo-root ${REPO_ROOT} -- ${PREUPLOAD_FILES}
[Builtin Hooks]
diff --git a/README.md b/README.md
index 1954d0a..cd56dc3 100644
--- a/README.md
+++ b/README.md
@@ -113,7 +113,10 @@ force your own quote handling.
Some variables are available to make it easier to handle OS differences. These
are automatically expanded for you:
-* `${REPO_ROOT}`: The absolute path of the root of the repo checkout.
+* `${REPO_ROOT}`: The absolute path of the root of the repo checkout. If the
+ project is in a submanifest, this points to the root of the submanifest.
+* `${REPO_OUTER_ROOT}`: The absolute path of the root of the repo checkout.
+ This always points to the root of the overall repo checkout.
* `${BUILD_OS}`: The string `darwin-x86` for macOS and the string `linux-x86`
for Linux/x86.
@@ -193,6 +196,9 @@ canned hooks already included geared towards AOSP style guidelines.
* `google_java_format`: Run Java code through
[`google-java-format`](https://github.com/google/google-java-format)
* `jsonlint`: Verify JSON code is sane.
+* `ktfmt`: Run Kotlin code through `ktfmt`. Supports an additional option
+ --include-dirs, which if specified will limit enforcement to only files under
+ the specified directories.
* `pylint`: Alias of `pylint2`. Will change to `pylint3` by end of 2019.
* `pylint2`: Run Python code through `pylint` using Python 2.
* `pylint3`: Run Python code through `pylint` using Python 3.
@@ -274,6 +280,7 @@ distros/versions. The following tools are recognized:
* `gofmt`: used for the `gofmt` builtin hook.
* `google-java-format`: used for the `google_java_format` builtin hook.
* `google-java-format-diff`: used for the `google_java_format` builtin hook.
+* `ktfmt`: used for the `ktfmt` builtin hook.
* `pylint`: used for the `pylint` builtin hook.
* `rustfmt`: used for the `rustfmt` builtin hook.
diff --git a/pre-upload.py b/pre-upload.py
index 7eb11b8..0109133 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
@@ -199,17 +199,24 @@ def _process_hook_results(results):
warning_ret if has_warning else None)
-def _get_project_config():
+def _get_project_config(from_git=False):
"""Returns the configuration for a project.
+ Args:
+ from_git: If true, we are called from git directly and repo should not be
+ used.
Expects to be called from within the project root.
"""
- global_paths = (
- # Load the global config found in the manifest repo.
- os.path.join(rh.git.find_repo_root(), '.repo', 'manifests'),
- # Load the global config found in the root of the repo checkout.
- rh.git.find_repo_root(),
- )
+ if from_git:
+ global_paths = (rh.git.find_repo_root(),)
+ else:
+ global_paths = (
+ # Load the global config found in the manifest repo.
+ (os.path.join(rh.git.find_repo_root(), '.repo', 'manifests')),
+ # Load the global config found in the root of the repo checkout.
+ rh.git.find_repo_root(),
+ )
+
paths = (
# Load the config for this git repo.
'.',
@@ -233,28 +240,30 @@ 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 '
'attempting to upload again.\n', file=sys.stderr)
-def _run_project_hooks_in_cwd(project_name, proj_dir, output, commit_list=None):
+def _run_project_hooks_in_cwd(project_name, proj_dir, output, from_git=False, commit_list=None):
"""Run the project-specific hooks in the cwd.
Args:
project_name: The name of this project.
proj_dir: The directory for this project (for passing on in metadata).
output: Helper for summarizing output/errors to the user.
+ from_git: If true, we are called from git directly and repo should not be
+ used.
commit_list: A list of commits to run hooks against. If None or empty
list then we'll automatically get the list of commits that would be
uploaded.
@@ -263,7 +272,7 @@ def _run_project_hooks_in_cwd(project_name, proj_dir, output, commit_list=None):
False if any errors were found, else True.
"""
try:
- config = _get_project_config()
+ config = _get_project_config(from_git)
except rh.config.ValidationError as e:
output.error('Loading config files', str(e))
return False
@@ -281,8 +290,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)
@@ -337,13 +345,15 @@ def _run_project_hooks_in_cwd(project_name, proj_dir, output, commit_list=None):
return ret
-def _run_project_hooks(project_name, proj_dir=None, commit_list=None):
+def _run_project_hooks(project_name, proj_dir=None, from_git=False, commit_list=None):
"""Run the project-specific hooks in |proj_dir|.
Args:
project_name: The name of project to run hooks for.
proj_dir: If non-None, this is the directory the project is in. If None,
we'll ask repo.
+ from_git: If true, we are called from git directly and repo should not be
+ used.
commit_list: A list of commits to run hooks against. If None or empty
list then we'll automatically get the list of commits that would be
uploaded.
@@ -358,11 +368,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)
@@ -374,6 +384,7 @@ def _run_project_hooks(project_name, proj_dir=None, commit_list=None):
# Hooks assume they are run from the root of the project.
os.chdir(proj_dir)
return _run_project_hooks_in_cwd(project_name, proj_dir, output,
+ from_git=from_git,
commit_list=commit_list)
finally:
output.finish()
@@ -409,22 +420,35 @@ 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)
-def _identify_project(path):
+def _identify_project(path, from_git=False):
"""Identify the repo project associated with the given path.
Returns:
A string indicating what project is associated with the path passed in or
a blank string upon failure.
"""
- cmd = ['repo', 'forall', '.', '-c', 'echo ${REPO_PROJECT}']
- return rh.utils.run(cmd, capture_output=True, cwd=path).stdout.strip()
+ if from_git:
+ cmd = ['git', 'rev-parse', '--show-toplevel']
+ project_path = rh.utils.run(cmd, capture_output=True).stdout.strip()
+ cmd = ['git', 'rev-parse', '--show-superproject-working-tree']
+ superproject_path = rh.utils.run(cmd, capture_output=True).stdout.strip()
+ module_path = project_path[len(superproject_path) + 1:]
+ cmd = ['git', 'config', '-f', '.gitmodules',
+ '--name-only', '--get-regexp', '^submodule\..*\.path$',
+ f"^{module_path}$"]
+ module_name = rh.utils.run(cmd, cwd=superproject_path,
+ capture_output=True).stdout.strip()
+ return module_name[len('submodule.'):-len(".path")]
+ else:
+ cmd = ['repo', 'forall', '.', '-c', 'echo ${REPO_PROJECT}']
+ return rh.utils.run(cmd, capture_output=True, cwd=path).stdout.strip()
def direct_main(argv):
@@ -440,6 +464,8 @@ def direct_main(argv):
BadInvocation: On some types of invocation errors.
"""
parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--git', action='store_true',
+ help='This hook is called from git instead of repo')
parser.add_argument('--dir', default=None,
help='The directory that the project lives in. If not '
'specified, use the git project root based on the cwd.')
@@ -461,18 +487,18 @@ 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)
+ opts.project = _identify_project(opts.dir, opts.git)
if not opts.project:
- parser.error("Repo couldn't identify the project of %s" % opts.dir)
+ parser.error(f"Couldn't identify the project of {opts.dir}")
- if _run_project_hooks(opts.project, proj_dir=opts.dir,
+ if _run_project_hooks(opts.project, proj_dir=opts.dir, from_git=opts.git,
commit_list=opts.commits):
return 0
return 1
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
diff --git a/rh/git.py b/rh/git.py
index ab1e35f..5496164 100644
--- a/rh/git.py
+++ b/rh/git.py
@@ -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()
@@ -181,17 +181,41 @@ def get_commit_desc(commit):
return rh.utils.run(cmd, capture_output=True).stdout
-def find_repo_root(path=None):
- """Locate the top level of this repo checkout starting at |path|."""
+def find_repo_root(path=None, outer=False):
+ """Locate the top level of this repo checkout starting at |path|.
+
+ Args:
+ outer: Whether to find the outermost manifest, or the sub-manifest.
+ """
if path is None:
path = os.getcwd()
orig_path = path
path = os.path.abspath(path)
+
+ # If we are working on a superproject instead of a repo client, use the
+ # result from git directly. For regular repo client, this would return
+ # empty string.
+ cmd = ['git', 'rev-parse', '--show-superproject-working-tree']
+ git_worktree_path = rh.utils.run(cmd, cwd=path, capture_output=True).stdout.strip()
+ if git_worktree_path:
+ return git_worktree_path
+
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}')
+
+ root = path
+ if not outer and os.path.isdir(os.path.join(root, '.repo', 'submanifests')):
+ # If there are submanifests, walk backward from path until we find the
+ # corresponding submanifest root.
+ abs_orig_path = os.path.abspath(orig_path)
+ parts = os.path.relpath(abs_orig_path, root).split(os.path.sep)
+ while parts and not os.path.isdir(
+ os.path.join(root, '.repo', 'submanifests', *parts, 'manifests')):
+ parts.pop()
+ path = os.path.join(root, *parts)
return path
diff --git a/rh/hooks.py b/rh/hooks.py
index 0b3bb29..7edda83 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):
@@ -130,10 +130,15 @@ class Placeholders(object):
@property
def var_REPO_ROOT(self):
- """The root of the repo checkout."""
+ """The root of the repo (sub-manifest) checkout."""
return rh.git.find_repo_root()
@property
+ def var_REPO_OUTER_ROOT(self):
+ """The root of the repo (outer) checkout."""
+ return rh.git.find_repo_root(outer=True)
+
+ @property
def var_BUILD_OS(self):
"""The build OS (see _get_build_os_name for details)."""
return _get_build_os_name()
@@ -397,14 +402,50 @@ def check_google_java_format(project, commit, _desc, _diff, options=None):
fixup_func=fixup_func)
+def check_ktfmt(project, commit, _desc, diff, options=None):
+ """Checks that kotlin files are formatted with ktfmt."""
+
+ include_dir_args = [x for x in options.args()
+ if x.startswith('--include-dirs=')]
+ include_dirs = [x[len('--include-dirs='):].split(',')
+ for x in include_dir_args]
+ patterns = [fr'^{x}/.*\.kt$' for dir_list in include_dirs
+ for x in dir_list]
+ if not patterns:
+ patterns = [r'\.kt$']
+
+ filtered = _filter_diff(diff, patterns)
+
+ if not filtered:
+ return None
+
+ args = [x for x in options.args() if x not in include_dir_args]
+
+ ktfmt = options.tool_path('ktfmt')
+ cmd = [ktfmt, '--dry-run'] + args + HookOptions.expand_vars(
+ ('${PREUPLOAD_FILES}',), filtered)
+ result = _run(cmd)
+ if result.stdout:
+ paths = [os.path.join(project.dir, x.file) for x in filtered]
+ error = (
+ f'\nKotlin files need formatting.\n'
+ 'To reformat the kotlin files in this commit:\n'
+ f'{ktfmt} {" ".join(paths)}'
+ )
+ fixup_func = _fixup_func_caller([ktfmt] + paths)
+ return [rh.results.HookResult('ktfmt', project, commit, error=error,
+ files=paths, fixup_func=fixup_func)]
+ return 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 +453,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 +479,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 +582,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 +598,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 +658,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 +682,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 +707,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 +741,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 +761,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 +783,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 +815,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 +840,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 +949,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 +990,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
@@ -1005,14 +1047,15 @@ BUILTIN_HOOKS = {
'commit_msg_bug_field': check_commit_msg_bug_field,
'commit_msg_changeid_field': check_commit_msg_changeid_field,
'commit_msg_prebuilt_apk_fields': check_commit_msg_prebuilt_apk_fields,
- 'commit_msg_test_field': check_commit_msg_test_field,
'commit_msg_relnote_field_format': check_commit_msg_relnote_field_format,
'commit_msg_relnote_for_current_txt':
check_commit_msg_relnote_for_current_txt,
+ 'commit_msg_test_field': check_commit_msg_test_field,
'cpplint': check_cpplint,
'gofmt': check_gofmt,
'google_java_format': check_google_java_format,
'jsonlint': check_json,
+ 'ktfmt': check_ktfmt,
'pylint': check_pylint2,
'pylint2': check_pylint2,
'pylint3': check_pylint3,
@@ -1033,6 +1076,7 @@ TOOL_PATHS = {
'gofmt': 'gofmt',
'google-java-format': 'google-java-format',
'google-java-format-diff': 'google-java-format-diff.py',
+ 'ktfmt': 'ktfmt',
'pylint': 'pylint',
'rustfmt': 'rustfmt',
}
diff --git a/rh/hooks_unittest.py b/rh/hooks_unittest.py
index 8466319..ba5c5fa 100755
--- a/rh/hooks_unittest.py
+++ b/rh/hooks_unittest.py
@@ -33,6 +33,11 @@ import rh.config
import rh.hooks
+# pylint: disable=unused-argument
+def mock_find_repo_root(path=None, outer=False):
+ return '/ ${BUILD_OS}' if outer else '/ ${BUILD_OS}/sub'
+
+
class HooksDocsTests(unittest.TestCase):
"""Make sure all hook features are documented.
@@ -48,7 +53,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 +71,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):
@@ -107,7 +112,8 @@ class PlaceholderTests(unittest.TestCase):
self.assertGreater(len(ret), 4)
self.assertIn('PREUPLOAD_COMMIT', ret)
- @mock.patch.object(rh.git, 'find_repo_root', return_value='/ ${BUILD_OS}')
+ @mock.patch.object(rh.git, 'find_repo_root',
+ side_effect=mock_find_repo_root)
def testExpandVars(self, _m):
"""Verify the replacement actually works."""
input_args = [
@@ -115,6 +121,8 @@ class PlaceholderTests(unittest.TestCase):
# We also make sure that things in ${REPO_ROOT} are not double
# expanded (which is why the return includes ${BUILD_OS}).
'${REPO_ROOT}/some/prog/REPO_ROOT/ok',
+ # Verify that ${REPO_OUTER_ROOT} is expanded.
+ '${REPO_OUTER_ROOT}/some/prog/REPO_OUTER_ROOT/ok',
# Verify lists are merged rather than inserted.
'${PREUPLOAD_FILES}',
# Verify each file is preceded with '--file=' prefix.
@@ -131,7 +139,8 @@ class PlaceholderTests(unittest.TestCase):
]
output_args = self.replacer.expand_vars(input_args)
exp_args = [
- '/ ${BUILD_OS}/some/prog/REPO_ROOT/ok',
+ '/ ${BUILD_OS}/sub/some/prog/REPO_ROOT/ok',
+ '/ ${BUILD_OS}/some/prog/REPO_OUTER_ROOT/ok',
'path1/file1',
'path2/file2',
'--file=path1/file1',
@@ -149,8 +158,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."""
@@ -167,10 +176,19 @@ class PlaceholderTests(unittest.TestCase):
self.assertEqual(self.replacer.get('PREUPLOAD_FILES'),
['path1/file1', 'path2/file2'])
- @mock.patch.object(rh.git, 'find_repo_root', return_value='/repo!')
+ @mock.patch.object(rh.git, 'find_repo_root')
+ def testREPO_OUTER_ROOT(self, m):
+ """Verify handling of REPO_OUTER_ROOT."""
+ m.side_effect=mock_find_repo_root
+ self.assertEqual(self.replacer.get('REPO_OUTER_ROOT'),
+ mock_find_repo_root(path=None, outer=True))
+
+ @mock.patch.object(rh.git, 'find_repo_root')
def testREPO_ROOT(self, m):
"""Verify handling of REPO_ROOT."""
- self.assertEqual(self.replacer.get('REPO_ROOT'), m.return_value)
+ m.side_effect=mock_find_repo_root
+ self.assertEqual(self.replacer.get('REPO_ROOT'),
+ mock_find_repo_root(path=None, outer=False))
@mock.patch.object(rh.hooks, '_get_build_os_name', return_value='vapier os')
def testBUILD_OS(self, m):
@@ -212,7 +230,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):
@@ -267,6 +285,20 @@ class UtilsTests(unittest.TestCase):
self.assertTrue(isinstance(ret, str))
self.assertNotEqual(ret, '')
+ def testSortedToolPaths(self):
+ """Check TOOL_PATHS is sorted."""
+ # This assumes dictionary key ordering matches insertion/definition
+ # order which Python 3.7+ has codified.
+ # https://docs.python.org/3.7/library/stdtypes.html#dict
+ self.assertEqual(list(rh.hooks.TOOL_PATHS), sorted(rh.hooks.TOOL_PATHS))
+
+ def testSortedBuiltinHooks(self):
+ """Check BUILTIN_HOOKS is sorted."""
+ # This assumes dictionary key ordering matches insertion/definition
+ # order which Python 3.7+ has codified.
+ # https://docs.python.org/3.7/library/stdtypes.html#dict
+ self.assertEqual(
+ list(rh.hooks.BUILTIN_HOOKS), sorted(rh.hooks.BUILTIN_HOOKS))
@mock.patch.object(rh.utils, 'run')
@@ -296,10 +328,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 +354,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."""
@@ -776,6 +808,30 @@ class BuiltinHooksTests(unittest.TestCase):
# TODO: Actually pass some valid/invalid json data down.
+ def test_ktfmt(self, mock_check, _mock_run):
+ """Verify the ktfmt builtin hook."""
+ # First call should do nothing as there are no files to check.
+ ret = rh.hooks.check_ktfmt(
+ self.project, 'commit', 'desc', (), options=self.options)
+ self.assertIsNone(ret)
+ self.assertFalse(mock_check.called)
+ # Check that .kt files are included by default.
+ diff = [rh.git.RawDiffEntry(file='foo.kt'),
+ rh.git.RawDiffEntry(file='bar.java'),
+ rh.git.RawDiffEntry(file='baz/blah.kt')]
+ ret = rh.hooks.check_ktfmt(
+ self.project, 'commit', 'desc', diff, options=self.options)
+ self.assertListEqual(ret[0].files, ['/.../repo/dir/foo.kt',
+ '/.../repo/dir/baz/blah.kt'])
+ diff = [rh.git.RawDiffEntry(file='foo/f1.kt'),
+ rh.git.RawDiffEntry(file='bar/f2.kt'),
+ rh.git.RawDiffEntry(file='baz/f2.kt')]
+ ret = rh.hooks.check_ktfmt(self.project, 'commit', 'desc', diff,
+ options=rh.hooks.HookOptions('hook name', [
+ '--include-dirs=foo,baz'], {}))
+ self.assertListEqual(ret[0].files, ['/.../repo/dir/foo/f1.kt',
+ '/.../repo/dir/baz/f2.kt'])
+
def test_pylint(self, mock_check, _mock_run):
"""Verify the pylint builtin hook."""
self._test_file_filter(mock_check, rh.hooks.check_pylint2,
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..bece0b2 100644
--- a/rh/shell.py
+++ b/rh/shell.py
@@ -15,6 +15,7 @@
"""Functions for working with shell code."""
import os
+import pathlib
import sys
_path = os.path.realpath(__file__ + '/../..')
@@ -65,26 +66,31 @@ def shell_quote(s):
Returns:
A safely (possibly quoted) string.
"""
+ # If callers pass down bad types, don't blow up.
if isinstance(s, bytes):
s = s.encode('utf-8')
+ elif isinstance(s, pathlib.PurePath):
+ return str(s)
+ elif not isinstance(s, str):
+ return repr(s)
# See if no quoting is needed so we can return the string as-is.
for c in 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 +163,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..f7d2bba 100755
--- a/rh/shell_unittest.py
+++ b/rh/shell_unittest.py
@@ -17,6 +17,7 @@
import difflib
import os
+from pathlib import Path
import sys
import unittest
@@ -40,8 +41,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 +66,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',
@@ -94,6 +95,18 @@ class ShellQuoteTest(DiffTestCase):
self._testData(aux, {k: k for k in tests_quote.values()}, False)
self._testData(aux, {k: k for k in tests_quote}, False)
+ def testPathlib(self):
+ """Verify pathlib is handled."""
+ self.assertEqual(rh.shell.shell_quote(Path('/')), '/')
+
+ def testBadInputs(self):
+ """Verify bad inputs do not crash."""
+ for arg, exp in (
+ (1234, '1234'),
+ (Exception('hi'), "Exception('hi')"),
+ ):
+ self.assertEqual(rh.shell.shell_quote(arg), exp)
+
class CmdToStrTest(DiffTestCase):
"""Test the cmd_to_str function."""
@@ -105,7 +118,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..7928dd5 100755
--- a/rh/utils_unittest.py
+++ b/rh/utils_unittest.py
@@ -17,6 +17,7 @@
import datetime
import os
+from pathlib import Path
import sys
import unittest
@@ -152,13 +153,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):
@@ -215,6 +216,13 @@ class RunCommandTests(unittest.TestCase):
self.assertNotEqual(0, err.returncode)
self.assertIn('a/b/c/d', str(err))
+ def test_pathlib(self):
+ """Verify pathlib arguments work."""
+ result = rh.utils.run(['true', Path('/')])
+ # Verify stringify behavior.
+ str(result)
+ self.assertEqual(result.cmdstr, 'true /')
+
if __name__ == '__main__':
unittest.main()
diff --git a/tools/android_test_mapping_format.py b/tools/android_test_mapping_format.py
index ef1a9b5..7780859 100755
--- a/tools/android_test_mapping_format.py
+++ b/tools/android_test_mapping_format.py
@@ -26,8 +26,8 @@ import argparse
import json
import os
import re
-from typing import Any, Dict
import sys
+from typing import Any, Dict
_path = os.path.realpath(__file__ + '/../..')
if sys.path[0] != _path:
@@ -77,7 +77,7 @@ def _filter_comments(json_data: str) -> str:
'\n' if _COMMENTS_RE.match(x) else x for x in json_data.splitlines())
-def _validate_import(entry: Dict[str, Any], test_mapping_file: str) -> None:
+def _validate_import(entry: Dict[str, Any], test_mapping_file: str):
"""Validates an import setting.
Args:
@@ -141,7 +141,7 @@ def _validate_test(test: Dict[str, Any], test_mapping_file: str) -> bool:
f'Failed entry: {option}')
-def process_file(test_mapping_file: str) -> None:
+def process_file(test_mapping_file: str):
"""Validates a TEST_MAPPING file content."""
try:
test_mapping_data = json.loads(_filter_comments(test_mapping_file))
@@ -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(f'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..24ef711 100755
--- a/tools/clang-format.py
+++ b/tools/clang-format.py
@@ -75,14 +75,20 @@ 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)
+ result = rh.utils.run(cmd, capture_output=True, check=False)
+ # Newer versions of git-clang-format will exit 1 when it worked. Assume a
+ # real failure is any exit code above 1, or any time stderr is used, or if
+ # it exited 1 but didn't produce anything useful to stdout. If it exited 0,
+ # then assume all is well and we'll attempt to parse its output below.
+ if (result.returncode > 1 or result.stderr or
+ (not result.stdout and result.returncode)):
+ print(f'clang-format failed:\ncmd: {result.cmdstr}\n'
+ f'stdout:\n{result.stdout}\nstderr:\n{result.stderr}',
+ file=sys.stderr)
print('\nPlease report this to the clang team.', file=sys.stderr)
return 1
@@ -110,9 +116,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'
+ f'{sys.argv[0]} --fix {rh.shell.cmd_to_str(argv)}')
return 1
return 0
diff --git a/tools/clang-format_unittest.py b/tools/clang-format_unittest.py
new file mode 100755
index 0000000..1128f65
--- /dev/null
+++ b/tools/clang-format_unittest.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unittests for clang-format."""
+
+import contextlib
+from pathlib import Path
+import sys
+import tempfile
+import unittest
+
+
+DIR = Path(__file__).resolve().parent
+sys.path.insert(0, str(DIR.parent))
+
+# We have to import our local modules after the sys.path tweak. We can't use
+# relative imports because this is an executable program, not a module.
+# pylint: disable=wrong-import-position
+import rh.utils
+
+
+CLANG_FORMAT = DIR / 'clang-format.py'
+
+
+@contextlib.contextmanager
+def git_clang_format(data: str):
+ """Create a fake git-clang-format script."""
+ with tempfile.TemporaryDirectory(prefix='repohooks-tests') as tempdir:
+ tempdir = Path(tempdir)
+ script = tempdir / 'git-clang-format-fake.sh'
+ script.write_text(f'#!/bin/sh\n{data}', encoding='utf-8')
+ script.chmod(0o755)
+ yield script
+
+
+def run_clang_format(script, args, **kwargs):
+ """Helper to run clang-format.py with fake git-clang-format script."""
+ kwargs.setdefault('capture_output', True)
+ return rh.utils.run(
+ [CLANG_FORMAT, '--git-clang-format', script] + args, **kwargs)
+
+
+class GitClangFormatExit(unittest.TestCase):
+ """Test git-clang-format parsing."""
+
+ def test_diff_exit_0_no_output(self):
+ """Test exit 0 w/no output."""
+ with git_clang_format('exit 0') as script:
+ result = run_clang_format(script, ['--working-tree'])
+ self.assertEqual(result.stdout, '')
+
+ def test_diff_exit_0_stderr(self):
+ """Test exit 0 w/stderr output."""
+ with git_clang_format('echo bad >&2; exit 0') as script:
+ with self.assertRaises(rh.utils.CalledProcessError) as e:
+ run_clang_format(script, ['--working-tree'])
+ self.assertIn('clang-format failed', e.exception.stderr)
+
+ def test_diff_exit_1_no_output(self):
+ """Test exit 1 w/no output."""
+ with git_clang_format('exit 1') as script:
+ with self.assertRaises(rh.utils.CalledProcessError) as e:
+ run_clang_format(script, ['--working-tree'])
+ self.assertIn('clang-format failed', e.exception.stderr)
+
+ def test_diff_exit_1_stderr(self):
+ """Test exit 1 w/stderr."""
+ with git_clang_format('echo bad >&2; exit 1') as script:
+ with self.assertRaises(rh.utils.CalledProcessError) as e:
+ run_clang_format(script, ['--working-tree'])
+ self.assertIn('clang-format failed', e.exception.stderr)
+
+ def test_diff_exit_2(self):
+ """Test exit 2."""
+ with git_clang_format('exit 2') as script:
+ with self.assertRaises(rh.utils.CalledProcessError) as e:
+ run_clang_format(script, ['--working-tree'])
+ self.assertIn('clang-format failed', e.exception.stderr)
+
+
+if __name__ == '__main__':
+ unittest.main()
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..f951981 100755
--- a/tools/google-java-format.py
+++ b/tools/google-java-format.py
@@ -66,19 +66,19 @@ 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(
+ f'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 +92,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(f'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