copy script/static-checks folder from tf-a-ci-scripts
This is a complete copy of the folder [1], based on working dir
from commit [2]. Some checks DO NOT apply to TF-M project, but we
introduce the code as it is from TF-A project for completeness and
then further commits will remove/change relevant tests.
[1] https://git.trustedfirmware.org/ci/tf-a-ci-scripts.git/tree/script/static-checks
[2] https://git.trustedfirmware.org/ci/tf-a-ci-scripts.git/commit/?id=a16c8e2b37e2a8446e586f45c3e9b406496ad7b8
Change-Id: I024c30dc59caa6e635fcd43b1a30086231b29bc3
Signed-off-by: Leonardo Sandoval <leonardo.sandoval@linaro.org>
diff --git a/script/static-checks/check-banned-api.py b/script/static-checks/check-banned-api.py
new file mode 100755
index 0000000..0bfadeb
--- /dev/null
+++ b/script/static-checks/check-banned-api.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2019, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import argparse
+import os
+import re
+import sys
+import utils
+
+# File extensions to check
+VALID_FILE_EXTENSIONS = ('.c', '.S', '.h')
+
+# Paths inside the tree to ignore. Hidden folders and files are always ignored.
+# They mustn't end in '/'.
+IGNORED_FOLDERS = (
+ "tools",
+ "docs"
+)
+
+# List of ignored files in folders that aren't ignored
+IGNORED_FILES = ()
+
+# Regular expression for searching the Banned APIs. This is taken from the
+# Coding guideline in TF-A repo
+BANNED_APIS = ["strcpy", "wcscpy", "strncpy", "strcat", "wcscat", "strncat",
+ "sprintf", "vsprintf", "strtok", "atoi", "atol", "atoll",
+ "itoa", "ltoa", "lltoa"]
+BANNED_PATTERN = re.compile('|'.join(BANNED_APIS))
+
+COMMENTS_PATTERN = re.compile(r"//|/\*|\*/")
+
+
+def filter_comments(f):
+ '''
+ filter_comments(f) -> iterator for line number, filtered line
+
+ Given an iterable of lines (such as a file), return another iterable of
+ lines, with the comments filtered out and removed.
+ '''
+
+ in_comment = False
+ for line_num, line in enumerate(f):
+ line = line.rstrip('\n')
+
+ temp = ""
+ breaker = len(line) if in_comment else 0
+ for match in COMMENTS_PATTERN.finditer(line):
+ content = match.group(0)
+ start, end = match.span()
+
+ if in_comment:
+ if content == "*/":
+ in_comment = False
+ breaker = end
+ else:
+ if content == "/*":
+ in_comment = True
+ temp += line[breaker:start]
+ breaker = len(line)
+ elif content == "//":
+ temp += line[breaker:start]
+ breaker = len(line)
+ break
+
+ temp += line[breaker:]
+ if temp:
+ yield line_num + 1, temp
+
+
+def file_check_banned_api(path, encoding='utf-8'):
+ '''
+ Reads all lines from a file in path and checks for any banned APIs.
+ The combined number of errors and uses of banned APIs is returned. If the
+ result is equal to 0, the file is clean and contains no banned APIs.
+ '''
+
+ count = 0
+
+ try:
+ f = open(path, encoding=encoding)
+ except FileNotFoundError:
+ print("ERROR: could not open " + path)
+ utils.print_exception_info()
+ return True
+
+ try:
+ for line_num, line in filter_comments(f):
+ match = BANNED_PATTERN.search(line)
+ if match:
+ location = "line {} of file {}".format(line_num, path)
+ print("BANNED API: in " + location)
+
+ # NOTE: this preview of the error is not perfect if comments
+ # have been removed - however, it does good enough most of the
+ # time.
+ start, end = match.span()
+ print(">>> {}".format(line))
+ print(" {}^{}".format(start * " ", (end - start - 1) * "~"))
+
+ count += 1
+ except:
+ print("ERROR: unexpected exception while parsing " + path)
+ utils.print_exception_info()
+ count += 1
+
+ f.close()
+
+ return count
+
+
+def get_tree_files():
+ '''
+ Get all files in the git repository
+ '''
+
+ # Get patches of the affected commits with one line of context.
+ (rc, stdout, stderr) = utils.shell_command(['git', 'ls-files'])
+ if rc != 0:
+ return False
+
+ lines = stdout.splitlines()
+ return lines
+
+
+def get_patch_files(base_commit, end_commit):
+ '''
+ Get all files that have changed in a given patch
+ '''
+
+ # Get patches of the affected commits with one line of context.
+ (rc, stdout, stderr) = utils.shell_command([
+ 'git', 'diff-tree', '--diff-filter=ACMRT', '-r', '--name-only',
+ base_commit, end_commit])
+
+ if rc != 0:
+ return False
+
+ paths = stdout.splitlines()
+ return paths
+
+
+def parse_cmd_line():
+ parser = argparse.ArgumentParser(
+ description="Check Banned APIs",
+ epilog="""
+ For each source file in the tree, checks whether Banned APIs as
+ described in the list are used or not.
+ """
+ )
+
+ parser.add_argument("--tree", "-t",
+ help="""
+ Path to the source tree to check (default: %(default)s)
+ """,
+ default=os.curdir)
+ parser.add_argument("--patch", "-p",
+ help="""
+ Patch mode. Instead of checking all files in
+ the source tree, the script will consider only files
+ that are modified by the latest patch(es).
+ """,
+ action="store_true")
+ parser.add_argument("--from-ref",
+ help="""
+ Base commit in patch mode (default: %(default)s)
+ """,
+ default="master")
+ parser.add_argument("--to-ref",
+ help="""
+ Final commit in patch mode (default: %(default)s)
+ """,
+ default="HEAD")
+ parser.add_argument("--verbose", "-v",
+ help="Print verbose output",
+ action="store_true")
+ args = parser.parse_args()
+ return args
+
+
+if __name__ == "__main__":
+ args = parse_cmd_line()
+
+ os.chdir(args.tree)
+
+ if args.patch:
+ print("Checking files modified between patches " + args.from_ref +
+ " and " + args.to_ref + "...\n")
+ files = get_patch_files(args.from_ref, args.to_ref)
+ else:
+ print("Checking all files git repo " + os.path.abspath(args.tree) +
+ "...\n")
+ files = get_tree_files()
+
+ total_errors = 0
+ for filename in files:
+ ignored = utils.file_is_ignored(filename, VALID_FILE_EXTENSIONS,
+ IGNORED_FILES, IGNORED_FOLDERS)
+ if ignored:
+ if args.verbose:
+ print("INFO: Skipping ignored file " + filename)
+ continue
+
+ if args.verbose:
+ print("INFO: Checking " + filename)
+
+ total_errors += file_check_banned_api(filename)
+
+ print(str(total_errors) + " errors found")
+
+ if total_errors == 0:
+ sys.exit(0)
+ else:
+ sys.exit(1)