#!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 # Copyright (c) 2021 Intel Corporation import os import sh import argparse import re from unidiff import PatchSet if "ZEPHYR_BASE" not in os.environ: exit("$ZEPHYR_BASE environment variable undefined.") RESERVED_NAMES_SCRIPT = "/scripts/coccinelle/reserved_names.cocci" coccinelle_scripts = [RESERVED_NAMES_SCRIPT, "/scripts/coccinelle/same_identifier.cocci", #"/scripts/coccinelle/identifier_length.cocci", ] coccinelle_reserved_names_exclude_regex = [ r"lib/libc/.*", r"lib/posix/.*", r"include/zephyr/posix/.*", ] def parse_coccinelle(contents: str, violations: dict): reg = re.compile("([a-zA-Z0-9_/]*\\.[ch]:[0-9]*)(:[0-9\\-]*: )(.*)") for line in contents.split("\n"): r = reg.match(line) if r: f = r.group(1) if f in violations: violations[f].append(r.group(3)) else: violations[r.group(1)] = [r.group(3)] def parse_args(): parser = argparse.ArgumentParser( description="Check commits against Cocccinelle rules", allow_abbrev=False) parser.add_argument('-r', "--repository", required=False, help="Path to repository") parser.add_argument('-c', '--commits', default=None, help="Commit range in the form: a..b") parser.add_argument("-o", "--output", required=False, help="Print violation into a file") return parser.parse_args() def main(): args = parse_args() if not args.commits: exit("missing commit range") if args.repository is None: repository_path = os.environ['ZEPHYR_BASE'] else: repository_path = args.repository sh_special_args = { '_tty_out': False, '_cwd': repository_path } # pylint does not like the 'sh' library # pylint: disable=too-many-function-args,unexpected-keyword-arg commit = sh.git("diff", args.commits, **sh_special_args) patch_set = PatchSet(commit) zephyr_base = os.getenv("ZEPHYR_BASE") violations = {} numViolations = 0 for f in patch_set: if not f.path.endswith(".c") and not f.path.endswith(".h") or not os.path.exists(zephyr_base + "/" + f.path): continue for script in coccinelle_scripts: skip_reserved_names = False if script == RESERVED_NAMES_SCRIPT: for path in coccinelle_reserved_names_exclude_regex: if re.match(path, f.path): skip_reserved_names = True break if skip_reserved_names: continue script_path =zephyr_base + "/" + script print(f"Running {script} on {f.path}") try: cocci = sh.coccicheck( "--mode=report", "--cocci=" + script_path, f.path, _timeout=10, **sh_special_args) parse_coccinelle(cocci, violations) except sh.TimeoutException: print("we timed out waiting, skipping...") for hunk in f: for line in hunk: if line.is_added: violation = "{}:{}".format(f.path, line.target_line_no) if violation in violations: numViolations += 1 if args.output: with open(args.output, "a+") as fp: fp.write("{}:{}\n".format( violation, "\t\n".join( violations[violation]))) else: print( "{}:{}".format( violation, "\t\n".join( violations[violation]))) return numViolations if __name__ == "__main__": ret = main() exit(ret)