blob: 0198504ee106fc3832ec1708ede8ec6951255c7f [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (c) 2019-2021, 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 = (
'platform/ext',
'bl2/ext',
'docs',
'lib',
'tools'
)
# 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="origin/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)