def test_parse_complex_cfg(): src_to_test = """ int main() { if(true){ test = 1; } var = 0; if(true){ if(true){ test = 2; }else{ test = 3; } }else{ test = 3; } } """ src_to_test_ast = parser.parse(src_to_test) main_ast = cast_lib.find_funcdef_node(src_to_test_ast, 'main') codecfg = cfg.ControlFlowGraph(main_ast) #codecfg.draw() assert len(codecfg.nodes()) == 16 assert len(codecfg.edges()) == 18 assert nx.is_directed_acyclic_graph(codecfg) assert_degrees(codecfg, 3)
def test_parse_while_no_loop(): src_to_test = """ int main(){ while(true){ if(true){ test = 2; } var = 0; } } """ src_to_test_ast = parser.parse(src_to_test) main_ast = cast_lib.find_funcdef_node(src_to_test_ast, 'main') codecfg = cfg.ControlFlowGraph(main_ast, loop=False) #codecfg.draw() assert len(codecfg.nodes()) == 9 assert len(codecfg.edges()) == 10 assert nx.is_directed_acyclic_graph(codecfg) assert_degrees(codecfg, 2)
def path_to_formula(src, theory): src1_path_ast = parser.parse(cast_lib.remove_c99_comments(src)) main_ast = cast_lib.find_funcdef_node(src1_path_ast,'main') srccfg = cfg.ControlFlowGraph(main_ast) path = list(srccfg.paths())[0] phi = PhaseFormula(theory, path) return phi
def complete_paths(suffix_path, prefix_paths): """ Generate all valid paths extending `prefix_paths` with `suffix path`""" complete_paths = [] for prefix_path in prefix_paths: # we don't prefix with ended paths, i.e. with continues count_continues = cast_lib.count_continues(prefix_path) if count_continues == 0: cfg_end = suffix_path.copy() # CFG path only containing ifs prefix_nodes = [ n for n in list(nx.topological_sort(prefix_path)) if type(n) == c_ast.If or type(n) == c_ast.While ] #prefix_nodes = list(nx.topological_sort(prefix_path)) # discard the node in common, the last #prefix_nodes = prefix_nodes[:-1] #prefix_nodes.append(c_ast.Break()) valid_prefix_path = cfg.ControlFlowGraph() nx.add_path(valid_prefix_path, prefix_nodes) # TODO: we could iterate the path and check if is SAT or # reduce redundant ifs if len(valid_prefix_path) > 0: nodes_beginning = list(nx.topological_sort(valid_prefix_path)) nodes_end = list(nx.topological_sort(cfg_end)) complete_path = nx.compose(valid_prefix_path, cfg_end) complete_path.add_edge(nodes_beginning[-1], nodes_end[0]) path = complete_path else: path = cfg_end complete_paths.append(path) return complete_paths
def test_parse_simple_cfg(): src_to_test = """ int main(){ while(true){ if(true){ test = 2; } var = 0; } } """ src_to_test_ast = parser.parse(src_to_test) main_ast = cast_lib.find_funcdef_node(src_to_test_ast, 'main') codecfg = cfg.ControlFlowGraph(main_ast) #codecfg.draw() assert len(codecfg.nodes()) == 9 assert len(codecfg.edges()) == 10 assert_degrees(codecfg, 1)
def prune_equivalent_paths(ast: c_ast.Node): theory = C99Theory(ast) code_ast = cast_lib.find_funcdef_node(ast, 'main') code_cfg = cfg.ControlFlowGraph(code_ast, loop=False) # collect the paths to_visit = phase_paths(code_cfg) # we split paths between mbox assignments and phase changes splitted_paths = [] for p in to_visit: splitted_path = [] chunk = [] for n_index, node in enumerate(p): chunk.append(node) phase_incremented = cast_lib.is_var_increment(node.astnode, 'view') phase_jump = cast_lib.is_var_assignment(node.astnode, 'view') path_end = type(node.astnode) == c_ast.Continue if (phase_incremented or phase_jump or path_end) and len(chunk) > 0: # add context from previous chunk if len(splitted_path) > 0: previous_chunk = splitted_path[-1] context_chunk = [] # we look for all asserts from the last elemento to the first mbox assignment for n_prev in previous_chunk[::-1]: if cast_lib.is_var_assignment(n_prev.astnode, 'mbox'): break if type(n_prev) == cfg.ControlFlowGraph.AssertNode: context_chunk.insert(0, n_prev) context_chunk.extend(chunk) chunk = context_chunk splitted_path.append(chunk) chunk = [] splitted_paths.append(splitted_path) # search for equivalent paths respecting the phase final_paths = [path[1] for path in splitted_paths] # BFS search of chunk equivalence max_splitted_path = max(splitted_paths, key=lambda i: len(i)) max_splitted_path_length = len(max_splitted_path) for depth in range(0, max_splitted_path_length): for path_split in splitted_paths: if len(path_split) > depth: chunk = path_split[depth] # look for an equivalent phase phi = PhaseFormula(theory, chunk) unique = True for p in final_paths: if p != chunk: phi_prime = PhaseFormula(theory, p) if phi == phi_prime: unique = False break if unique: final_paths.append(chunk)
def async_to_sync(async_ast: c_ast.Node, config): """ Given a c99 code in a AST form, returns the corresponding code of its synchronous equivalent program. Notes ----- First step is calculate all code between round variables assigments, then we add all the context needed to reach that piece of code. Entry point if ... if ... Current round Next round * --------------------------------> *----------------------> * A B The code we want to extract is in path B, but we need to collect all the conditions to reach this path, this is obtained collection all c_ast.If in path A. Path A and B can't contain c_ast.Continue nodes. Path B can't contain other round assigments in the middle. """ phase_variable = config['phase'] round_variable = config['round'] labels = config['labels'] # we discard what we won't use main_ast = cast_lib.find_funcdef_node(async_ast, 'main') cast_lib.map_dfs(main_ast, cast_lib.replace_while_with_body, []) cast_lib.filter_nodes(main_ast, remove_declarations) codecfg = cfg.ControlFlowGraph(main_ast) # we search paths between every (monotonically increasing) assigment of round variables paths_between_rounds = paths_between_round_assignments( codecfg, labels, round_variable, phase_variable) # for every protocol's round we calculate all possible paths including its previous context (e.g., ifs conditions) start_node = list(nx.topological_sort(codecfg))[0] complete_paths_by_round = {} for round_label, suffix_paths in paths_between_rounds.items(): complete_paths_by_round[round_label] = [] for suffix_path in suffix_paths: suffix_first_node = list(nx.topological_sort(suffix_path))[0] prefix_paths = get_cfg_paths_between_nodes(codecfg, start_node, suffix_first_node) cp = complete_paths(suffix_path, prefix_paths) complete_paths_by_round[round_label].extend(cp) # the code of a round is the (graph) union of all the complete paths found to belong to that round # the easiest approach is to remove the nodes not included in those paths from the original code using the coord property sync_code = {} for round_label, paths in complete_paths_by_round.items(): round_code_cfg = cfg.ControlFlowGraph() nodes_to_keep = set() for p in paths: for n in p.nodes(): nodes_to_keep.add(str(n.coord)) round_sync_code = copy.deepcopy(main_ast) cast_lib.filter_nodes(round_sync_code, node_coord_not_in_set, nodes_to_keep) sync_code[round_label] = round_sync_code # translate to CompHO compho = {} for round_label, ast_code in sync_code.items(): compho[round_label] = {} ast_send = c_ast.FileAST([copy.deepcopy(ast_code)]) get_compho_send(ast_send) ast_update = c_ast.FileAST([copy.deepcopy(ast_code)]) get_compho_update(ast_update, round_variable, round_label) compho[round_label]['send'] = ast_send compho[round_label]['update'] = ast_update return compho