diff --git a/share/analysis-scripts/build_callgraph.py b/share/analysis-scripts/build_callgraph.py index c62294d1b8ed1eac1168881b0fda0479413e7a32..8d8cbe6d5b224126970f70a9141a143d49e74dff 100755 --- a/share/analysis-scripts/build_callgraph.py +++ b/share/analysis-scripts/build_callgraph.py @@ -42,6 +42,8 @@ if len(sys.argv) < 2: else: files = sys.argv[1:] +debug = os.getenv("DEBUG") + class Callgraph: """ Heuristics-based callgraphs. @@ -89,6 +91,8 @@ def compute(files): called = call[0] line = call[1] loc = (f, line) + if debug: + print(f"build_callgraph: {f}:{line}: {caller} -> {called}") cg.add_edge(caller, called, loc) #print(f"Callgraph computed ({len(cg.succs)} node(s), {len(cg.edges)} edge(s))") return cg @@ -115,25 +119,30 @@ def print_cg_dot(cg, out=sys.stdout): # n: input, not modified # # The difference between visited and just_visited is that the latter refers -# to the current dfs; nodes visited in previous dfs already had their cycles +# to the current bfs; nodes visited in previous bfs already had their cycles # reported, so we do not report them multiple times. -def cycle_dfs(cg, visited, just_visited, n): +def cycle_bfs(cg, visited, just_visited, n): + if debug: + print(f"cycle_bfs: visited = {visited}, just_visited = {just_visited}, n = {n}") just_visited.add(n) if n not in cg.succs: return [] + fifo = [] for succ in cg.succs[n]: if succ in just_visited: return [(n, succ)] elif succ in visited: # already reported in a previous iteration - return [] - else: - res = cycle_dfs(cg, visited, just_visited, succ) - if res: - caller = res[0][0] - return [(n, caller)] + res - else: - return [] + continue + fifo.append(succ) + # no cycle found with direct successors, go to next level + for succ in fifo: + my_just_visited = just_visited.copy() + res = cycle_bfs(cg, visited, my_just_visited, succ) + if res: + # note that other cycles may not be reported + caller = res[0][0] + return [(n, caller)] + res return [] def compute_recursive_cycles(cg, acc): @@ -144,7 +153,7 @@ def compute_recursive_cycles(cg, acc): while to_visit: just_visited = set() n = sorted(list(to_visit))[0] - cycle = cycle_dfs(cg, visited, just_visited, n) + cycle = cycle_bfs(cg, visited, just_visited, n) visited = visited.union(just_visited) if cycle: (fst, snd) = cycle[0] @@ -153,10 +162,7 @@ def compute_recursive_cycles(cg, acc): to_visit -= visited def detect_recursion(cg): - #print(f"Detecting recursive calls...") to_visit = set(cg.nodes()) - #if len(to_visit) > 100: - # print(f"Checking recursion ({len(to_visit)} nodes)...") if not to_visit: # empty graph -> no recursion return False visited = set() @@ -164,7 +170,7 @@ def detect_recursion(cg): while to_visit: just_visited = set() n = sorted(list(to_visit))[0] - cycle = cycle_dfs(cg, visited, just_visited, n) + cycle = cycle_bfs(cg, visited, just_visited, n) visited = visited.union(just_visited) if cycle: has_cycle = True diff --git a/share/analysis-scripts/estimate_difficulty.py b/share/analysis-scripts/estimate_difficulty.py index fe4a2b78c49ef835dd1e91801d9be256ca4583f2..64e84e4e43a55b4264cc3f7aaca0ac20056d389b 100755 --- a/share/analysis-scripts/estimate_difficulty.py +++ b/share/analysis-scripts/estimate_difficulty.py @@ -36,6 +36,11 @@ import subprocess import sys import tempfile +#TODO : avoid relativizing paths when introducing too many ".." ; +#TODO : accept directory as argument (--full-tree), and then do glob **/*.{c,i} inside +#TODO : try to check the presence of compiler builtins +#TODO : try to check for pragmas + MIN_PYTHON = (3, 5) if sys.version_info < MIN_PYTHON: sys.exit("Python %s.%s or later is required.\n" % MIN_PYTHON) diff --git a/share/analysis-scripts/function_finder.py b/share/analysis-scripts/function_finder.py index cfa761757e3e1bd34493e318a530eddcba4d620e..2a5e1e8213b38b89ce7ad90bcb9e3760612af860 100755 --- a/share/analysis-scripts/function_finder.py +++ b/share/analysis-scripts/function_finder.py @@ -151,23 +151,29 @@ def find_definitions_and_declarations(want_defs, want_decls, filename, newlines) else: if not want_defs: continue - end = get_first_line_after(braces, start) - if not end: - # no closing braces found; use "single-line function heuristic": - # assume the function is defined as 'type f(...) { code; }', - # in a single line - def_start_newline_offset = newlines[start-1] - line_of_opening_brace = line_of_offset(newlines, match.start(2)) - definition = content[match.start(1):newlines[start-1]] - if start == line_of_opening_brace and definition.rstrip().endswith("}"): - # assume the '}' is closing the '{' from the same line - end = line_of_opening_brace - else: - # no opening brace; assume a false positive and skip definition - print(f"{os.path.relpath(filename)}:{start}:closing brace not found, " + - f"skipping potential definition of '{funcname}'") - continue + definition = content[match.start(1):newlines[start-1]] + # try "single-line function heuristic": + # assume the function is defined as 'type f(...) { code; }', + # in a single line + if definition.strip().endswith("}"): + end = line_of_offset(newlines, match.start(2)) + else: + end = get_first_line_after(braces, start) + if not end: + # no closing braces found; try again the "single-line function heuristic" + def_start_newline_offset = newlines[start-1] + line_of_opening_brace = line_of_offset(newlines, match.start(2)) + if start == line_of_opening_brace and definition.rstrip().endswith("}"): + # assume the '}' is closing the '{' from the same line + end = line_of_opening_brace + else: + # no opening brace; assume a false positive and skip definition + print(f"{os.path.relpath(filename)}:{start}:closing brace not found, " + + f"skipping potential definition of '{funcname}'") + continue terminator_offset = match.start(2) + if debug: + print(f"function_finder: {'def' if is_def else 'decl'} of {funcname} between {start} and {end}") res.append((funcname, is_def, start, end, terminator_offset)) return res diff --git a/share/analysis-scripts/heuristic_list_functions.py b/share/analysis-scripts/heuristic_list_functions.py index 86df5e9d7fcc53324371749b4dcaf65d4e10c4c6..7f47635a3055c212364f415d6a8817dc99aaa8f4 100755 --- a/share/analysis-scripts/heuristic_list_functions.py +++ b/share/analysis-scripts/heuristic_list_functions.py @@ -43,8 +43,15 @@ if len(sys.argv) < 4: print(" in the specified files.") sys.exit(1) -want_defs = sys.argv[1] -want_decls = sys.argv[2] +def boolish_string(s): + if s.lower() == "true" or s == "1": + return True + if s.lower() == "false" or s == "0": + return False + sys.exit(f"error: expected 'true', 'false', 0 or 1; got: {s}") + +want_defs = boolish_string(sys.argv[1]) +want_decls = boolish_string(sys.argv[2]) files = sys.argv[3:] for f in files: diff --git a/tests/fc_script/list_functions.i b/tests/fc_script/list_functions.i index f30547abb73355730c35f523382b6b37a21f7aea..aab65784552e38fdecd78fed6fd1ead8744f654d 100644 --- a/tests/fc_script/list_functions.i +++ b/tests/fc_script/list_functions.i @@ -1,4 +1,4 @@ /* run.config NOFRAMAC: testing frama-c-script, not frama-c itself - EXECNOW: LOG list_functions.res LOG list_functions.err bin/frama-c-script heuristic-list-functions @PTEST_DIR@/*.c @PTEST_DIR@/*.i > @PTEST_DIR@/result/list_functions.res 2> @PTEST_DIR@/result/list_functions.err + EXECNOW: LOG heuristic_list_functions.res LOG heuristic_list_functions.err bin/frama-c-script heuristic-list-functions true true @PTEST_DIR@/*.c @PTEST_DIR@/*.i > @PTEST_DIR@/result/heuristic_list_functions.res 2> @PTEST_DIR@/result/heuristic_list_functions.err */ diff --git a/tests/fc_script/oracle/heuristic_list_functions.err b/tests/fc_script/oracle/heuristic_list_functions.err new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/fc_script/oracle/heuristic_list_functions.res b/tests/fc_script/oracle/heuristic_list_functions.res new file mode 100644 index 0000000000000000000000000000000000000000..fe8495493cd286842024661ea2ed28407625a805 --- /dev/null +++ b/tests/fc_script/oracle/heuristic_list_functions.res @@ -0,0 +1,50 @@ +tests/fc_script/for-find-fun.c:6:7: main2 (declaration) +tests/fc_script/for-find-fun.c:13:17: main3 (definition) +tests/fc_script/for-find-fun2.c:5:7: main3 (declaration) +tests/fc_script/for-find-fun2.c:10:12: f (definition) +tests/fc_script/for-find-fun2.c:14:17: g (definition) +tests/fc_script/for-find-fun2.c:19:21: h (definition) +tests/fc_script/for-find-fun2.c:28:31: static_fun (definition) +tests/fc_script/for-list-functions.c:8:15: static_fun (definition) +tests/fc_script/for-list-functions.c:17:22: k (definition) +tests/fc_script/main.c:12:14: main (definition) +tests/fc_script/main2.c:6:8: fake_main (definition) +tests/fc_script/main2.c:10:12: domain (definition) +tests/fc_script/main2.c:14:16: main2 (definition) +tests/fc_script/main3.c:6:9: main (definition) +tests/fc_script/make-wrapper.c:7:7: defined (declaration) +tests/fc_script/make-wrapper.c:9:9: specified (declaration) +tests/fc_script/make-wrapper.c:11:11: external (declaration) +tests/fc_script/make-wrapper.c:13:18: main (definition) +tests/fc_script/make-wrapper2.c:5:7: defined (definition) +tests/fc_script/make-wrapper2.c:13:13: specified (declaration) +tests/fc_script/make-wrapper2.c:16:16: external (declaration) +tests/fc_script/make-wrapper3.c:7:9: external (definition) +tests/fc_script/build-callgraph.i:7:9: main (definition) +tests/fc_script/build-callgraph.i:16:16: fn2 (declaration) +tests/fc_script/build-callgraph.i:18:23: fn1 (definition) +tests/fc_script/build-callgraph.i:26:32: main1 (definition) +tests/fc_script/build-callgraph.i:34:36: main2 (definition) +tests/fc_script/build-callgraph.i:46:49: main3 (definition) +tests/fc_script/build-callgraph.i:52:63: main4 (definition) +tests/fc_script/build-callgraph.i:64:69: main (definition) +tests/fc_script/recursions.i:8:10: g (definition) +tests/fc_script/recursions.i:12:15: f (definition) +tests/fc_script/recursions.i:17:20: h (definition) +tests/fc_script/recursions.i:22:24: i (definition) +tests/fc_script/recursions.i:26:28: j (definition) +tests/fc_script/recursions.i:30:30: l (declaration) +tests/fc_script/recursions.i:31:31: m (declaration) +tests/fc_script/recursions.i:33:35: k (definition) +tests/fc_script/recursions.i:37:39: l (definition) +tests/fc_script/recursions.i:41:43: m (definition) +tests/fc_script/recursions.i:45:46: norec (definition) +tests/fc_script/recursions.i:48:50: direct_rec (definition) +tests/fc_script/recursions.i:52:52: indirect_rec1 (declaration) +tests/fc_script/recursions.i:54:56: indirect_rec2 (definition) +tests/fc_script/recursions.i:58:60: indirect_rec1 (definition) +tests/fc_script/recursions.i:62:62: decl_only (declaration) +tests/fc_script/recursions.i:64:64: one_liner_function (definition) +tests/fc_script/recursions.i:66:66: multiple_indirect1 (declaration) +tests/fc_script/recursions.i:68:71: multiple_indirect2 (definition) +tests/fc_script/recursions.i:73:76: multiple_indirect1 (definition) diff --git a/tests/fc_script/oracle/recursions.res b/tests/fc_script/oracle/recursions.res index 784a72aeb5ffd1c48837607bd3707eff14280562..af11f5fead4d3586b970c25f0d6301a1ad632348 100644 --- a/tests/fc_script/oracle/recursions.res +++ b/tests/fc_script/oracle/recursions.res @@ -1,8 +1,18 @@ +recursive cycle detected: + tests/fc_script/recursions.i:49: direct_rec -> direct_rec recursive cycle detected: tests/fc_script/recursions.i:13: f -> f recursive cycle detected: tests/fc_script/recursions.i:18: h -> h +recursive cycle detected: + tests/fc_script/recursions.i:59: indirect_rec1 -> indirect_rec2 + tests/fc_script/recursions.i:55: indirect_rec2 -> indirect_rec1 recursive cycle detected: tests/fc_script/recursions.i:34: k -> l tests/fc_script/recursions.i:38: l -> m tests/fc_script/recursions.i:42: m -> k +recursive cycle detected: + tests/fc_script/recursions.i:74: multiple_indirect1 -> multiple_indirect2 + tests/fc_script/recursions.i:75: multiple_indirect1 -> multiple_indirect2 + tests/fc_script/recursions.i:69: multiple_indirect2 -> multiple_indirect1 + tests/fc_script/recursions.i:70: multiple_indirect2 -> multiple_indirect1 diff --git a/tests/fc_script/recursions.i b/tests/fc_script/recursions.i index d471e8c461858cf371f040e13d2abb1e1763b0fa..841d609939737908e7378823b9642e55af7fb2bb 100644 --- a/tests/fc_script/recursions.i +++ b/tests/fc_script/recursions.i @@ -44,3 +44,33 @@ void m() { void norec() { } + +int direct_rec() { + return direct_rec(); +} + +void indirect_rec1(); + +void indirect_rec2() { + indirect_rec1(); +} + +void indirect_rec1() { + indirect_rec2(); +} + +void decl_only(); + +void one_liner_function() { decl_only(); } + +void multiple_indirect1(); + +void multiple_indirect2() { + multiple_indirect1(); + multiple_indirect1(); +} + +void multiple_indirect1() { + multiple_indirect2(); + multiple_indirect2(); +}