0001-Add-initial-coccicheck-script.v5.patch
text/x-patch
Filename: 0001-Add-initial-coccicheck-script.v5.patch
Type: text/x-patch
Part: 0
From 5fa03693cca229dc2228348e642a243eff6c670a Mon Sep 17 00:00:00 2001
From: Mats Kindahl <mats@kindahl.net>
Date: Sun, 29 Dec 2024 19:35:58 +0100
Subject: [PATCH 1/7] Add initial coccicheck script
The coccicheck.py script can be used to run several semantics patches on a
source tree to either generate a report, see the context of the modification
(what lines that requires changes), or generate a patch to correct an issue.
usage: coccicheck.py [-h] [--verbose] [--spatch SPATCH]
[--spflags SPFLAGS]
[--mode {patch,report,context}] [--jobs JOBS]
[--include DIR] [--patchdir DIR]
pattern path [path ...]
positional arguments:
pattern Pattern for Cocci files to use.
path Directory or source path to process.
options:
-h, --help show this help message and exit
--verbose, -v
--spatch SPATCH Path to spatch binary. Defaults to value of
environment variable SPATCH.
--spflags SPFLAGS Flags to pass to spatch call. Defaults to
value of enviroment variable SPFLAGS.
--mode {patch,report,context}
Mode to use for coccinelle. Defaults to
value of environment variable MODE.
--jobs JOBS Number of jobs to use for spatch. Defaults
to value of environment variable JOBS.
--include DIR, -I DIR
Extra include directories.
--patchdir DIR Path for which patch should be created
relative to.
---
src/tools/coccicheck.py | 185 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 185 insertions(+)
create mode 100755 src/tools/coccicheck.py
diff --git a/src/tools/coccicheck.py b/src/tools/coccicheck.py
new file mode 100755
index 00000000000..838f8184c54
--- /dev/null
+++ b/src/tools/coccicheck.py
@@ -0,0 +1,185 @@
+#!/usr/bin/env python3
+
+"""Run Coccinelle on a set of files and directories.
+
+This is a re-written version of the Linux ``coccicheck`` script.
+
+Coccicheck can run in two different modes (the original have four
+different modes):
+
+- *patch*: patch files using the cocci file.
+
+- *report*: report will report any improvements that this script can
+ make, but not show any patch.
+
+- *context*: show the context where the patch can be applied.
+
+The program will take a single cocci file and call spatch(1) with a
+set of paths that can be either files or directories.
+
+When starting, the cocci file will be parsed and any lines containing
+"Options:" or "Requires:" will be treated specially.
+
+- Lines containing "Options:" will have a list of options to add to
+ the call of the spatch(1) program. These options will be added last.
+
+- Lines containing "Requires:" can contain a version of spatch(1) that
+ is required for this cocci file. If the version requirements are not
+ satisfied, the file will not be used.
+
+When calling spatch(1), it will set the virtual rules "patch",
+"report", or "context" and the cocci file can use these to act
+differently depending on the mode.
+
+The following environment variables can be set:
+
+SPATCH: Path to spatch program. This will be used if no path is
+ passed using the option --spatch.
+
+SPFLAGS: Extra flags to use when calling spatch. These will be added
+ last.
+
+MODE: Mode to use. It will be used if no --mode is passed to
+ coccicheck.py.
+
+"""
+
+import argparse
+import os
+import sys
+import subprocess
+import re
+
+from pathlib import PurePath, Path
+from packaging import version
+
+VERSION_CRE = re.compile(
+ r'spatch version (\S+) compiled with OCaml version (\S+)'
+)
+
+
+def parse_metadata(cocci_file):
+ """Parse metadata in Cocci file."""
+ metadata = {}
+ with open(cocci_file) as fh:
+ for line in fh:
+ mre = re.match(r'(Options|Requires):(.*)', line, re.IGNORECASE)
+ if mre:
+ metadata[mre.group(1).lower()] = mre.group(2)
+ return metadata
+
+
+def get_config(args):
+ """Compute configuration information."""
+ # Figure out spatch version. We just need to read the first line
+ config = {}
+ cmd = [args.spatch, '--version']
+ with subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True) as proc:
+ for line in proc.stdout:
+ mre = VERSION_CRE.match(line)
+ if mre:
+ config['spatch_version'] = mre.group(1)
+ break
+ return config
+
+
+def run_spatch(cocci_file, args, config, env):
+ """Run coccinelle on the provided file."""
+ if args.verbose > 1:
+ print("processing cocci file", cocci_file)
+ spatch_version = config['spatch_version']
+ metadata = parse_metadata(cocci_file)
+
+ # Check that we have a valid version
+ if 'required' in metadata:
+ required_version = version.parse(metadata['required'])
+ if required_version < spatch_version:
+ print(
+ f'Skipping SmPL patch {cocci_file}: '
+ f'requires {required_version} (had {spatch_version})'
+ )
+ return
+
+ command = [
+ args.spatch,
+ "-D", args.mode,
+ "--cocci-file", cocci_file,
+ "--very-quiet",
+ ]
+
+ if 'options' in metadata:
+ command.append(metadata['options'])
+ if args.mode == 'report':
+ command.append('--no-show-diff')
+ if args.patchdir:
+ command.extend(['--patch', args.patchdir])
+ if args.jobs:
+ command.extend(['--jobs', args.jobs])
+ if args.spflags:
+ command.append(args.spflags)
+
+ for path in args.path:
+ subprocess.run(command + [path], env=env, check=True)
+
+
+def coccinelle(args, config, env):
+ """Run coccinelle on all files matching the provided pattern."""
+ root = '/' if PurePath(args.cocci).is_absolute() else '.'
+ count = 0
+ for cocci_file in Path(root).glob(args.cocci):
+ count += 1
+ run_spatch(cocci_file, args, config, env)
+ return count
+
+
+def main(argv):
+ """Run coccicheck."""
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--verbose', '-v', action='count', default=0)
+ parser.add_argument('--spatch', type=PurePath, metavar='SPATCH',
+ default=os.environ.get('SPATCH'),
+ help=('Path to spatch binary. Defaults to '
+ 'value of environment variable SPATCH.'))
+ parser.add_argument('--spflags', type=PurePath,
+ metavar='SPFLAGS',
+ default=os.environ.get('SPFLAGS', None),
+ help=('Flags to pass to spatch call. Defaults '
+ 'to value of enviroment variable SPFLAGS.'))
+ parser.add_argument('--mode', choices=['patch', 'report', 'context'],
+ default=os.environ.get('MODE', 'report'),
+ help=('Mode to use for coccinelle. Defaults to '
+ 'value of environment variable MODE.'))
+ parser.add_argument('--jobs', default=os.environ.get('JOBS', None),
+ help=('Number of jobs to use for spatch. Defaults to '
+ 'value of environment variable JOBS.'))
+ parser.add_argument('--include', '-I', type=PurePath,
+ metavar='DIR',
+ help='Extra include directories.')
+ parser.add_argument('--patchdir', type=PurePath, metavar='DIR',
+ help=('Path for which patch should be created '
+ 'relative to.'))
+ parser.add_argument('cocci', metavar='pattern',
+ help='Pattern for Cocci files to use.')
+ parser.add_argument('path', nargs='+', type=PurePath,
+ help='Directory or source path to process.')
+
+ args = parser.parse_args(argv)
+
+ if args.verbose > 1:
+ print("arguments:", args)
+
+ if args.spatch is None:
+ parser.error('spatch is part of the Coccinelle project and is '
+ 'available at http://coccinelle.lip6.fr/')
+
+ if coccinelle(args, get_config(args), os.environ) == 0:
+ parser.error(f'no coccinelle files found matching {args.cocci}')
+
+
+if __name__ == '__main__':
+ try:
+ main(sys.argv[1:])
+ except KeyboardInterrupt:
+ print("Execution aborted")
+ except Exception as exc:
+ print(exc)
--
2.43.0