| #!/usr/bin/env python3 |
| # |
| # Copyright (c) 2019-2020 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) |