#!/usr/bin/env python3 from typing import Dict, List, Sequence, Set, Tuple import subprocess import sys def cflow(filenames: Sequence[str]) -> str: args = [ 'cflow', '-AArd2', *filenames ] print(' '.join(f'"{x}"' for x in args)) p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=None, universal_newlines=True) return p.communicate()[0] CallGraph = Dict[str, List[str]] def build_call_graph(cflow_output: str) -> CallGraph: call_graph = {} cur = None for line in cflow_output.split('\n'): if not line: continue indented = line[0] == ' ' line = line.strip().split(None, 1)[0] if line.endswith(':'): line = line[:-1] if not indented: if line in call_graph: print(f"Warning: {line} redefined") else: call_graph[line] = [] cur = line else: call_graph[cur].append(line) return call_graph def recursive_callers(call_graph: CallGraph, start: str ) -> Tuple[Set[str],Dict[str, str]]: result = set() to_process = [start] paths = {start: start} while to_process: caller = to_process.pop() if caller in result: continue result.add(caller) if caller in call_graph: to_process += call_graph[caller] for c in call_graph[caller]: paths[c] = f'{c} -> {paths[caller]}' return result, paths def check_conflict(call_graph: CallGraph, checked_class: str, disallowed: Set[str], disallowed_class: str, disallowed_paths: Dict[str, str]) -> None: if checked_class not in call_graph: return for fn in call_graph[checked_class]: if fn in disallowed: print(f'Error: {fn} is {checked_class}, but calls {disallowed_class} code') print(f' {disallowed_paths[fn]}') print() def main() -> None: filenames = sys.argv[1:] cflow_output = cflow(filenames) call_graph = build_call_graph(cflow_output) calling_gs, paths_gs = recursive_callers(call_graph, 'GLOBAL_STATE_CODE()') calling_io_or_gs, paths_io_or_gs = recursive_callers(call_graph, 'IO_OR_GS_CODE()') calling_io, paths_io = recursive_callers(call_graph, 'IO_CODE()') check_conflict(call_graph, 'IO_CODE()', calling_gs, 'GLOBAL_STATE_CODE()', paths_gs) check_conflict(call_graph, 'IO_CODE()', calling_io_or_gs, 'IO_OR_GS_CODE()', paths_io_or_gs) check_conflict(call_graph, 'IO_OR_GS_CODE()', calling_gs, 'GLOBAL_STATE_CODE()', paths_gs) if __name__ == '__main__': main()