From 6da1b894661f1138b004c41d28105f73daa0ba98 Mon Sep 17 00:00:00 2001 From: Andre Maroneze <andre.maroneze@cea.fr> Date: Fri, 19 Jul 2024 15:05:09 +0200 Subject: [PATCH] [analysis-scripts] add more typing annotations --- share/analysis-scripts/build.py | 6 ++-- share/analysis-scripts/detect_recursion.py | 3 +- share/analysis-scripts/estimate_difficulty.py | 4 +-- share/analysis-scripts/external_tool.py | 18 +++++----- share/analysis-scripts/function_finder.py | 35 ++++++++++++------- .../heuristic_list_functions.py | 5 +-- 6 files changed, 41 insertions(+), 30 deletions(-) diff --git a/share/analysis-scripts/build.py b/share/analysis-scripts/build.py index d912dc3d824..22ea1a474ff 100755 --- a/share/analysis-scripts/build.py +++ b/share/analysis-scripts/build.py @@ -252,7 +252,7 @@ def copy_fc_stubs() -> Path: # Returns pairs (line_number, has_args) for each likely definition of # [funcname] in [filename]. # [has_args] is used to distinguish between main(void) and main(int, char**). -def find_definitions(funcname: str, filename: str) -> list[tuple[str, bool]]: +def find_definitions(funcname: str, filename: Path) -> list[tuple[int, bool]]: file_content = source_filter.open_and_filter( Path(filename), not under_test and do_filter_source ) @@ -261,7 +261,7 @@ def find_definitions(funcname: str, filename: str) -> list[tuple[str, bool]]: defs = function_finder.find_definitions_and_declarations( True, False, filename, file_content, file_lines, newlines, funcname ) - res = [] + res: list[tuple[int, bool]] = [] for d in defs: defining_line = file_lines[d[2] - 1] after_funcname = defining_line[defining_line.find(funcname) + len(funcname) :] @@ -367,7 +367,7 @@ if unknown_sources: # We also need to check if the main function uses a 'main(void)'-style # signature, to patch fc_stubs.c. -main_definitions: dict[Path, list[tuple[Path, str, bool]]] = {} +main_definitions: dict[Path, list[tuple[Path, int, bool]]] = {} for target, sources in sorted(sources_map.items()): main_definitions[target] = [] for source in sources: diff --git a/share/analysis-scripts/detect_recursion.py b/share/analysis-scripts/detect_recursion.py index a25078aae0b..a02790df505 100755 --- a/share/analysis-scripts/detect_recursion.py +++ b/share/analysis-scripts/detect_recursion.py @@ -25,6 +25,7 @@ """This script finds files containing likely declarations and definitions for a given function name, via heuristic syntactic matching.""" +from pathlib import Path import sys import build_callgraph @@ -34,7 +35,7 @@ if len(sys.argv) < 2: print(" prints a heuristic callgraph for the specified files.") sys.exit(1) else: - files = sys.argv[1:] + files = set([Path(f) for f in sys.argv[1:]]) cg = build_callgraph.compute(files) build_callgraph.detect_recursion(cg) diff --git a/share/analysis-scripts/estimate_difficulty.py b/share/analysis-scripts/estimate_difficulty.py index 47437f56cbd..82c6c4264dd 100755 --- a/share/analysis-scripts/estimate_difficulty.py +++ b/share/analysis-scripts/estimate_difficulty.py @@ -261,8 +261,8 @@ if not no_cloc: data = external_tool.run_and_check( [cloc, "--hide-rate", "--progress-rate=0", "--csv"] + list(str(f) for f in files), "" ) - data = data.splitlines() - [nfiles, _sum, nblank, ncomment, ncode] = data[-1].split(",") + datas = data.splitlines() + [nfiles, _sum, nblank, ncomment, ncode] = datas[-1].split(",") nlines = int(nblank) + int(ncomment) + int(ncode) logging.info( "Processing %d file(s), approx. %d lines of code (out of %d lines)", diff --git a/share/analysis-scripts/external_tool.py b/share/analysis-scripts/external_tool.py index d1a1b3584e0..717980c6380 100644 --- a/share/analysis-scripts/external_tool.py +++ b/share/analysis-scripts/external_tool.py @@ -37,24 +37,24 @@ emit_warns = os.getenv("PTESTS_TESTING") is None cached_commands: dict[str, Path | None] = {} -def resource_path(relative_path): +def resource_path(relative_path: str) -> str: """Get absolute path to resource; only used by the pyinstaller standalone distribution""" base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) return os.path.join(base_path, relative_path) -def get_command(command, env_var_name): +def get_command(command: str, env_var_name: str) -> Path | None: """Returns a Path to the command; priority goes to the environment variable, then in the PATH, then in the resource directory (for a pyinstaller binary).""" if command in cached_commands: return cached_commands[command] - p = os.getenv(env_var_name) - if p: - p = Path(p) + p_str = os.getenv(env_var_name) + if p_str: + p = Path(p_str) else: - p = shutil.which(command) - if p: - p = Path(p) + p_str = shutil.which(command) + if p_str: + p = Path(p_str) else: p = Path(resource_path(command)) if not p.exists(): @@ -68,7 +68,7 @@ def get_command(command, env_var_name): return p -def run_and_check(command_and_args, input_data): +def run_and_check(command_and_args, input_data: str) -> str: try: return subprocess.check_output( command_and_args, diff --git a/share/analysis-scripts/function_finder.py b/share/analysis-scripts/function_finder.py index e6410a688bd..e9e84f5545c 100644 --- a/share/analysis-scripts/function_finder.py +++ b/share/analysis-scripts/function_finder.py @@ -24,6 +24,7 @@ """Exports find_function_in_file, to be used by other scripts""" +from pathlib import Path import bisect import os import re @@ -55,7 +56,7 @@ debug = os.getenv("DEBUG", False) # Precomputes the regex for 'fname' -def prepare_re_specific_name(fname): +def prepare_re_specific_name(fname: str) -> re.Pattern: re_fun = re.compile( "^" + optional_type_prefix @@ -72,9 +73,9 @@ def prepare_re_specific_name(fname): # Returns 0 if not found, 1 if declaration, 2 if definition -def find_specific_name(prepared_re, f): +def find_specific_name(prepared_re: re.Pattern, f: Path) -> int: with open(f, encoding="ascii", errors="ignore") as data: - file_content = data.read() + file_content: str = data.read() has_decl_or_def = prepared_re.search(file_content) if has_decl_or_def is None: return 0 @@ -86,7 +87,7 @@ def find_specific_name(prepared_re, f): # matches function definitions or declarations # if funcname is not None, only matches for the specified # function name -def compute_re_def_or_decl(funcname): +def compute_re_def_or_decl(funcname: str) -> re.Pattern: ident = funcname if funcname else c_identifier return re.compile( "^" @@ -110,7 +111,7 @@ re_funcall = re.compile("(" + c_identifier + ")" + whitespace + r"\(") # Computes the offset (in bytes) of each '\n' in the file, # returning them as a list -def compute_newline_offsets(file_lines): +def compute_newline_offsets(file_lines: list[str]) -> list[int]: offsets = [] current = 0 for line in file_lines: @@ -122,7 +123,7 @@ def compute_newline_offsets(file_lines): # Returns the line number (starting at 1) containing the character # of offset [offset]. # [offsets] is the sorted list of offsets for newline characters in the file. -def line_of_offset(offsets, offset): +def line_of_offset(offsets: list[int], offset: int) -> int: return bisect.bisect_right(offsets, offset) + 1 @@ -132,7 +133,7 @@ def line_of_offset(offsets, offset): # This is a heuristic to attempt to detect function closing braces: # it assumes that the first '}' (without preceding whitespace) after a # function definition denotes its closing brace. -def compute_closing_braces(file_lines): +def compute_closing_braces(file_lines: list[str]) -> list[int]: braces = [] for i, line in enumerate(file_lines, start=1): # note: lines contain '\n', so they are never empty @@ -153,7 +154,7 @@ def compute_closing_braces(file_lines): # closing braces were found). # # [line_numbers] must be sorted in ascending order. -def get_first_line_after(line_numbers, n): +def get_first_line_after(line_numbers: list[int], n: int) -> int | None: try: return line_numbers[bisect.bisect_left(line_numbers, n)] except IndexError: @@ -172,8 +173,14 @@ def get_first_line_after(line_numbers, n): # itself and avoid considering it as a call. For function definitions, # this is the opening brace; for function declarations, this is the semicolon. def find_definitions_and_declarations( - want_defs, want_decls, filename, file_content, file_lines, newlines, funcname=None -): + want_defs: bool, + want_decls: bool, + filename: Path, + file_content: str, + file_lines: list[str], + newlines: list[int], + funcname=None, +) -> list[tuple[str, bool, int, int, int]]: braces = compute_closing_braces(file_lines) res = [] re_fundef_or_decl = compute_re_def_or_decl(funcname) @@ -199,8 +206,10 @@ def find_definitions_and_declarations( if definition.strip().endswith("}"): end = line_of_offset(newlines, terminator_offset) else: - end = get_first_line_after(braces, start) - if not end: + maybe_end = get_first_line_after(braces, start) + if maybe_end: + end = maybe_end + else: # no closing braces found; try again the "single-line function heuristic" line_of_opening_brace = line_of_offset(newlines, terminator_offset) if start == line_of_opening_brace and definition.rstrip()[-1] == "}": @@ -230,7 +239,7 @@ calls_blacklist = ["if", "while", "for", "return", "sizeof", "switch", "_Alignas # # Note: this may include the function prototype itself; # it must be filtered by the caller. -def find_calls(file_content, newlines): +def find_calls(file_content: str, newlines: list[int]) -> list[tuple[str, int, int]]: # create a list of Match objects that fit "pattern" regex res = [] for match in re.finditer(re_funcall, file_content): diff --git a/share/analysis-scripts/heuristic_list_functions.py b/share/analysis-scripts/heuristic_list_functions.py index 5caddd0a32d..87614435156 100755 --- a/share/analysis-scripts/heuristic_list_functions.py +++ b/share/analysis-scripts/heuristic_list_functions.py @@ -25,6 +25,7 @@ """This script uses heuristics to list all function definitions and declarations in a set of files.""" +from pathlib import Path import sys import os import function_finder @@ -39,7 +40,7 @@ if len(sys.argv) < 4: sys.exit(1) -def boolish_string(s): +def boolish_string(s: str) -> bool: if s.lower() == "true" or s == "1": return True if s.lower() == "false" or s == "0": @@ -49,7 +50,7 @@ def boolish_string(s): want_defs = boolish_string(sys.argv[1]) want_decls = boolish_string(sys.argv[2]) -files = sys.argv[3:] +files = set([Path(f) for f in sys.argv[3:]]) for f in sorted(files): with open(f, encoding="ascii", errors="ignore") as data: -- GitLab