def vertices(): yield [ ComparisonGraph.Vertex(dup("f"), Result.Kind.EQUAL, ("/test/f1/1.ll", "/test/f2/2.ll")), ComparisonGraph.Vertex(dup("h"), Result.Kind.NOT_EQUAL, ("/test/f1/1.ll", "/test/f2/3.ll")), ComparisonGraph.Vertex(dup("g"), Result.Kind.NOT_EQUAL, ("/test/f1/1.ll", "/test/f2/2.ll")) ]
def test_cachability_reset_after_absorb(graph_uncachable): """Tests whether the cachable attribute is reset to true after replacing the assumed equal vertex causing the uncachability.""" graph_uncachable.normalize() graph_uncachable.populate_predecessor_lists() graph_uncachable.mark_uncachable_from_assumed_equal() assert not graph_uncachable["f2"].cachable graph_to_merge = ComparisonGraph() graph_to_merge["f3"] = ComparisonGraph.Vertex(dup("f3"), Result.Kind.NOT_EQUAL, dup("app/f2.c"), dup(20)) graph_uncachable.absorb_graph(graph_to_merge) assert graph_uncachable["f2"].cachable
def test_add_vertex_strong(graph): """Tests adding a strong vertex to the graph.""" graph["test"] = ComparisonGraph.Vertex(dup("test"), Result.Kind.EQUAL, dup("app/main.c"), (81, 82)) assert "test" in graph.vertices assert graph["test"].names == dup("test") assert graph["test"].result == Result.Kind.EQUAL assert graph["test"].files == dup("app/main.c") assert graph["test"].lines == (81, 82) assert graph["test"] not in graph._weak_vertex_cache
def test_add_vertex_weak(graph): """Tests adding a weak vertex to the graph.""" graph["test.void"] = ComparisonGraph.Vertex(("test", "test.void"), Result.Kind.EQUAL, dup("app/main.c"), (81, 82)) assert "test.void" in graph.vertices assert graph["test.void"].names == ("test", "test.void") assert graph["test.void"].result == Result.Kind.EQUAL assert graph["test.void"].files == dup("app/main.c") assert graph["test.void"].lines == (81, 82) assert graph["test.void"] in graph._weak_vertex_cache
def test_add_edge_weak(graph): """Tests adding a weak edge to a graph.""" graph.add_edge(graph["main_function"], ComparisonGraph.Side.LEFT, ComparisonGraph.Edge("strength", "app/main.c", 61)) graph.add_edge(graph["main_function"], ComparisonGraph.Side.RIGHT, ComparisonGraph.Edge("strength.void", "app/main.c", 61)) left_succesor_names = [ edge.target_name for edge in graph["main_function"].successors[ ComparisonGraph.Side.LEFT] ] right_successor_names = [ edge.target_name for edge in graph["main_function"].successors[ ComparisonGraph.Side.RIGHT] ] assert "strength" in left_succesor_names assert "strength.void" in right_successor_names assert "strength" not in right_successor_names assert "strength.void" not in left_succesor_names assert "strength.void" in [ edge.target_name for edge in graph._normalize_edge_cache ]
def test_add_edge_strong(graph): """Tests adding a strong edge to a graph.""" for side in ComparisonGraph.Side: graph.add_edge(graph["main_function"], side, ComparisonGraph.Edge("missing", "app/main.c", 61)) assert "missing" in [ edge.target_name for edge in graph["main_function"].successors[side] ] assert "missing" not in [ edge.target_name for edge in graph._normalize_edge_cache ]
def run_simpll(first, second, fun_first, fun_second, var, suffix=None, cache_dir=None, control_flow_only=False, output_llvm_ir=False, print_asm_diffs=False, verbose=False, use_ffi=False): """ Simplify modules to ease their semantic difference. Uses the SimpLL tool. :return A tuple containing the two LLVM IR files generated by SimpLL followed by the result of the comparison in the form of a graph and a list of missing function definitions. """ stderr = None if not verbose: stderr = open(os.devnull, "w") first_out_name = add_suffix(first, suffix) if suffix else first second_out_name = add_suffix(second, suffix) if suffix else second if use_ffi: output = ffi.new("char [1000000]") conf_struct = ffi.new("struct config *") cache_dir = ffi.new("char []", cache_dir.encode("ascii") if cache_dir else b"") variable = ffi.new("char []", var.encode("ascii") if var else b"") conf_struct = ffi.new("struct config *") conf_struct.CacheDir = cache_dir conf_struct.ControlFlowOnly = control_flow_only conf_struct.OutputLlvmIR = output_llvm_ir conf_struct.PrintAsmDiffs = print_asm_diffs conf_struct.PrintCallStacks = True conf_struct.Variable = variable conf_struct.Verbose = verbose conf_struct.VerboseMacros = False module_left = ffi.new("char []", first.encode("ascii")) module_right = ffi.new("char []", second.encode("ascii")) module_left_out = ffi.new("char []", first_out_name.encode("ascii")) module_right_out = ffi.new("char []", second_out_name.encode("ascii")) fun_left = ffi.new("char []", fun_first.encode("ascii")) fun_right = ffi.new("char []", fun_second.encode("ascii")) try: lib.runSimpLL(module_left, module_right, module_left_out, module_right_out, fun_left, fun_right, conf_struct[0], output) simpll_out = ffi.string(output) except ffi.error: raise SimpLLException("Simplifying files failed") else: try: # Determine the SimpLL binary to use. # The manually built one has priority over the installed one. if os.path.isfile("build/diffkemp/simpll/diffkemp-simpll"): simpll_bin = "build/diffkemp/simpll/diffkemp-simpll" else: simpll_bin = "diffkemp-simpll" # SimpLL command simpll_command = list([simpll_bin, first, second, "--print-callstacks"]) # Main (analysed) functions simpll_command.append("--fun") if fun_first != fun_second: simpll_command.append("{},{}".format(fun_first, fun_second)) else: simpll_command.append(fun_first) # Analysed variable if var: simpll_command.extend(["--var", var]) # Suffix for output files if suffix and output_llvm_ir: simpll_command.extend(["--suffix", suffix]) # Cache directory with equal function pairs if cache_dir: simpll_command.extend(["--cache-dir", cache_dir]) if control_flow_only: simpll_command.append("--control-flow") if output_llvm_ir: simpll_command.append("--output-llvm-ir") if print_asm_diffs: simpll_command.append("--print-asm-diffs") if verbose: simpll_command.append("--verbose") print(" ".join(simpll_command)) simpll_out = check_output(simpll_command) except CalledProcessError: raise SimpLLException("Simplifying files failed") if output_llvm_ir: check_call(["opt", "-S", "-deadargelim", "-o", first_out_name, first_out_name], stderr=stderr) check_call(["opt", "-S", "-deadargelim", "-o", second_out_name, second_out_name], stderr=stderr) first_out = LlvmKernelModule(first_out_name) second_out = LlvmKernelModule(second_out_name) missing_defs = None try: result_graph = ComparisonGraph() simpll_result = yaml.safe_load(simpll_out) if simpll_result is not None: if "function-results" in simpll_result: for fun_result in simpll_result["function-results"]: # Create the vertex from the result and insert it into # the graph. vertex = ComparisonGraph.Vertex.from_yaml( fun_result, result_graph) # Prefer pointed name to ensure that a difference # contaning the variant function as either the left or # the right side has its name in the key. # This is useful because one can tell this is a weak # vertex from its name. if "." in vertex.names[ComparisonGraph.Side.LEFT]: result_graph[vertex.names[ ComparisonGraph.Side.LEFT]] = vertex else: result_graph[vertex.names[ ComparisonGraph.Side.RIGHT]] = vertex result_graph.normalize() result_graph.populate_predecessor_lists() result_graph.mark_uncachable_from_assumed_equal() missing_defs = simpll_result["missing-defs"] \ if "missing-defs" in simpll_result else None except yaml.YAMLError: pass return first_out, second_out, result_graph, missing_defs
def graph_uncachable(): """Graph used to test the marking of uncachable vertices.""" graph = ComparisonGraph() graph["f1"] = ComparisonGraph.Vertex(dup("f1"), Result.Kind.EQUAL, dup("app/f1.c"), dup(10)) graph["f2"] = ComparisonGraph.Vertex(dup("f2"), Result.Kind.EQUAL, dup("include/h1.h"), dup(20)) graph["f3"] = ComparisonGraph.Vertex(dup("f3"), Result.Kind.ASSUMED_EQUAL, dup("app/f2.c"), dup(20)) for side in ComparisonGraph.Side: graph.add_edge(graph["f1"], side, ComparisonGraph.Edge("f2", "app/f1.c", 11)) graph.add_edge(graph["f2"], side, ComparisonGraph.Edge("f3", "include/h1.c", 21)) yield graph
def test_absort_graph(graph): """Tests the absorb graph function, especially whether vertex replacing works properly.""" new_graph = ComparisonGraph() # This vertex should replace the old one (which is assumed equal). new_graph["missing"] = ComparisonGraph.Vertex(dup("missing"), Result.Kind.NOT_EQUAL, dup("app/mod.c"), dup(665)) # This vertex should not replace the old one. new_graph["do_check"] = ComparisonGraph.Vertex(dup("do_check"), Result.Kind.EQUAL, dup("app/mod.c"), dup(665)) # This vertex should replace the old one (it has more successors). new_graph["strength"] = ComparisonGraph.Vertex(dup("strength"), Result.Kind.NOT_EQUAL, dup("app/test.h"), (5, 5)) for side in ComparisonGraph.Side: new_graph.add_edge(new_graph["strength"], side, ComparisonGraph.Edge("missing", "app/w.c", 6)) new_graph.add_edge(new_graph["strength"], side, ComparisonGraph.Edge("main_function", "app/w.c", 7)) graph.absorb_graph(new_graph) assert graph["missing"].result == Result.Kind.NOT_EQUAL assert graph["do_check"].result == Result.Kind.NOT_EQUAL assert graph["strength"].result == Result.Kind.NOT_EQUAL
def graph(): g = ComparisonGraph() # Vertices g["main_function"] = ComparisonGraph.Vertex(dup("main_function"), Result.Kind.EQUAL, dup("app/main.c"), dup(51)) g["side_function"] = ComparisonGraph.Vertex(dup("side_function"), Result.Kind.EQUAL, dup("app/main.c"), dup(255)) g["do_check"] = ComparisonGraph.Vertex(dup("do_check"), Result.Kind.NOT_EQUAL, dup("app/main.c"), dup(105)) g["missing"] = ComparisonGraph.Vertex(dup("missing"), Result.Kind.ASSUMED_EQUAL, dup("app/mod.c"), dup(665)) g["looping"] = ComparisonGraph.Vertex(dup("looping"), Result.Kind.EQUAL, dup("app/main.c"), (81, 82)) # Weak variant of "strength" function vertex (e.g. void-returning on the # right side) g["strength.void"] = ComparisonGraph.Vertex(("strength", "strength.void"), Result.Kind.EQUAL, dup("app/main.c"), (5, 5)) # Strong variant of "strength" functin vertex g["strength"] = ComparisonGraph.Vertex( ("strength", "strength"), Result.Kind.EQUAL, dup("app/test.h"), (5, 5)) # Non-function differences g["do_check"].nonfun_diffs.append( ComparisonGraph.SyntaxDiff( "MACRO", "do_check", dup([ { "function": "_MACRO", "file": "test.c", "line": 1 }, { "function": "__MACRO", "file": "test.c", "line": 2 }, { "function": "___MACRO", "file": "test.c", "line": 3 }, ]), ("5", "5L"))) g["do_check"].nonfun_diffs.append( ComparisonGraph.TypeDiff( "struct file", "do_check", dup([ { "function": "struct file (type)", "file": "include/file.h", "line": 121 }, ]), dup("include/file.h"), dup(121))) # Edges for side in ComparisonGraph.Side: g.add_edge(g["main_function"], side, ComparisonGraph.Edge("do_check", "app/main.c", 58)) g.add_edge(g["main_function"], side, ComparisonGraph.Edge("side_function", "app/main.c", 59)) g.add_edge(g["do_check"], side, ComparisonGraph.Edge("missing", "app/main.c", 60)) g.add_edge(g["do_check"], side, ComparisonGraph.Edge("looping", "app/main.c", 74)) g.add_edge(g["looping"], side, ComparisonGraph.Edge("main_function", "app/main.c", 85)) # Strong call of "strength" g.add_edge(g["looping"], side, ComparisonGraph.Edge("strength", "app/main.c", 86)) g.add_edge(g["strength"], side, ComparisonGraph.Edge("missing", "app/w.c", 6)) # Weak call of "strength" g.add_edge(g["side_function"], ComparisonGraph.Side.LEFT, ComparisonGraph.Edge("strength", "app/main.c", 260)) g.add_edge(g["side_function"], ComparisonGraph.Side.RIGHT, ComparisonGraph.Edge("strength.void", "app/main.c", 260)) yield g
def run_simpll(first, second, fun_first, fun_second, var, suffix=None, cache_dir=None, pattern_config=None, control_flow_only=False, output_llvm_ir=False, print_asm_diffs=False, verbosity=0, use_ffi=False, module_cache=None, modules_to_cache=None): """ Simplify modules to ease their semantic difference. Uses the SimpLL tool. :return A tuple containing the two LLVM IR files generated by SimpLL followed by the result of the comparison in the form of a graph and a list of missing function definitions. """ stderr = None if verbosity == 0: stderr = open(os.devnull, "w") first_out_name = add_suffix(first, suffix) if suffix else first second_out_name = add_suffix(second, suffix) if suffix else second if use_ffi: # Preload module if in modules_to_cache if modules_to_cache is None: modules_to_cache = [] for module in [first, second]: if module not in module_cache and module in modules_to_cache: module_cache[module] = SimpLLModule(module) module_cache[module].preprocess(control_flow_only) use_cached_modules = (module_cache and first in module_cache and second in module_cache) output = ffi.new("char [1000000]") cache_dir = ffi.new("char []", cache_dir.encode("ascii") if cache_dir else b"") patterns = ffi.new( "char []", pattern_config.path.encode("ascii") if pattern_config else b"") variable = ffi.new("char []", var.encode("ascii") if var else b"") conf_struct = ffi.new("struct config *") conf_struct.CacheDir = cache_dir conf_struct.Patterns = patterns conf_struct.ControlFlowOnly = control_flow_only conf_struct.OutputLlvmIR = output_llvm_ir conf_struct.PrintAsmDiffs = print_asm_diffs conf_struct.PrintCallStacks = True conf_struct.Variable = variable conf_struct.Verbosity = verbosity if use_cached_modules: module_left = module_cache[first].pointer module_right = module_cache[second].pointer else: module_left = ffi.new("char []", first.encode("ascii")) module_right = ffi.new("char []", second.encode("ascii")) module_left_out = ffi.new("char []", first_out_name.encode("ascii")) module_right_out = ffi.new("char []", second_out_name.encode("ascii")) fun_left = ffi.new("char []", fun_first.encode("ascii")) fun_right = ffi.new("char []", fun_second.encode("ascii")) try: if use_cached_modules: r, w = os.pipe() pid = os.fork() if pid == 0: # Child process - run SimpLL and send result through pipe os.close(r) lib.runSimpLL(module_left, module_right, module_left_out, module_right_out, fun_left, fun_right, conf_struct[0], output) os.write(w, ffi.string(output)) os.close(w) os._exit(0) else: # Parent process - collect result from pipe os.close(w) r = os.fdopen(r) simpll_out = r.read() _, status = os.waitpid(pid, 0) if status != 0: raise SimpLLException("Simplifying files failed") else: lib.parseAndRunSimpLL(module_left, module_right, module_left_out, module_right_out, fun_left, fun_right, conf_struct[0], output) simpll_out = ffi.string(output) lib.shutdownSimpLL() except ffi.error: raise SimpLLException("Simplifying files failed") else: try: # Determine the SimpLL binary to use. # The manually built one has priority over the installed one. simpll_bin = "{}/diffkemp/simpll/diffkemp-simpll".format( get_simpll_build_dir()) if not os.path.isfile(simpll_bin): simpll_bin = "diffkemp-simpll" # SimpLL command simpll_command = list( [simpll_bin, first, second, "--print-callstacks"]) # Main (analysed) functions simpll_command.append("--fun") if fun_first != fun_second: simpll_command.append("{},{}".format(fun_first, fun_second)) else: simpll_command.append(fun_first) # Analysed variable if var: simpll_command.extend(["--var", var]) # Suffix for output files if suffix and output_llvm_ir: simpll_command.extend(["--suffix", suffix]) # Cache directory with equal function pairs if cache_dir: simpll_command.extend(["--cache-dir", cache_dir]) # Difference pattern configuration path if pattern_config: simpll_command.extend( ["--pattern-config", pattern_config.path]) if control_flow_only: simpll_command.append("--control-flow") if output_llvm_ir: simpll_command.append("--output-llvm-ir") if print_asm_diffs: simpll_command.append("--print-asm-diffs") if verbosity > 0: simpll_command.extend(["--verbosity", str(verbosity)]) print(" ".join(simpll_command)) simpll_out = check_output(simpll_command) except CalledProcessError: raise SimpLLException("Simplifying files failed") if output_llvm_ir: check_call([ "opt", "-S", "-deadargelim", "-o", first_out_name, first_out_name ], stderr=stderr) check_call([ "opt", "-S", "-deadargelim", "-o", second_out_name, second_out_name ], stderr=stderr) first_out = LlvmModule(first_out_name) second_out = LlvmModule(second_out_name) missing_defs = None try: result_graph = ComparisonGraph() simpll_result = yaml.safe_load(simpll_out) if simpll_result is not None: if "function-results" in simpll_result: for fun_result in simpll_result["function-results"]: # Create the vertex from the result and insert it into # the graph. vertex = ComparisonGraph.Vertex.from_yaml( fun_result, result_graph) # Prefer pointed name to ensure that a difference # contaning the variant function as either the left or # the right side has its name in the key. # This is useful because one can tell this is a weak # vertex from its name. if "." in vertex.names[ComparisonGraph.Side.LEFT]: result_graph[vertex.names[ ComparisonGraph.Side.LEFT]] = vertex else: result_graph[vertex.names[ ComparisonGraph.Side.RIGHT]] = vertex result_graph.normalize() result_graph.populate_predecessor_lists() result_graph.mark_uncachable_from_assumed_equal() missing_defs = simpll_result["missing-defs"] \ if "missing-defs" in simpll_result else None except yaml.YAMLError: pass return first_out, second_out, result_graph, missing_defs