diff --git a/CMakeLists.txt b/CMakeLists.txt index 82c8958e7..720f093e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) link_directories(${LLVM_LIBRARY_DIRS}) set(CMAKE_CXX_STANDARD 17) -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) +list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) include(GNUInstallDirs) diff --git a/cmake/FindZ3.cmake b/cmake/FindZ3.cmake index d63f967c9..d5a026c07 100644 --- a/cmake/FindZ3.cmake +++ b/cmake/FindZ3.cmake @@ -5,7 +5,7 @@ # Z3_CXX_INCLUDE_DIRS - the z3 C++ include directory # Z3_LIBRARIES - Link these to use libbpf -find_path (Z3_CXX_INCLUDE_DIRS z3++.h) +find_path (Z3_CXX_INCLUDE_DIRS z3++.h PATH_SUFFIXES z3) find_library (Z3_LIBRARIES z3) diff --git a/diffkemp/building/build_kernel.py b/diffkemp/building/build_kernel.py index 218742e41..9c4680990 100644 --- a/diffkemp/building/build_kernel.py +++ b/diffkemp/building/build_kernel.py @@ -4,6 +4,7 @@ from diffkemp.snapshot import Snapshot from diffkemp.building.build_utils import ( generate_from_function_list, + add_funcs_for_globvar, read_symbol_list, EMSG_EMPTY_SYMBOL_LIST) import errno @@ -97,7 +98,8 @@ def generate_from_sysctl_list(snapshot, sysctl_list): # Proc handler function for sysctl _add_proc_handler(snapshot, sysctl, proc_fun) # Adds functions which use the sysctl data variable - _add_funcs_for_data(snapshot, sysctl, sysctl_mod, proc_fun) + data = sysctl_mod.get_data(sysctl) + add_funcs_for_globvar(snapshot, sysctl, data, proc_fun) def _add_proc_handler(snapshot, sysctl, proc_fun): @@ -118,34 +120,3 @@ def _add_proc_handler(snapshot, sysctl, proc_fun): snapshot.source_tree.source_dir))) except SourceNotFoundException: print(" could not build proc handler") - - -def _add_funcs_for_data(snapshot, sysctl, sysctl_mod, proc_fun): - data = sysctl_mod.get_data(sysctl) - - if not data: - return - - for module in \ - snapshot.source_tree.get_modules_using_symbol(data.name): - # For now, we only support the x86 architecture in kernel - if "/arch/" in module.llvm and \ - "/arch/x86/" not in module.llvm: - continue - - curr_mod_path = os.path.relpath(module.llvm, - snapshot.source_tree.source_dir) - for func in module.get_functions_using_param(data): - if func == proc_fun: - continue - - snapshot.add_fun( - name=func, - llvm_mod=module, - glob_var=data.name, - tag="using data variable \"{}\"".format(data.name), - group=sysctl) - print(" {}: {} (using data variable \"{}\")".format( - func, - curr_mod_path, - data.name)) diff --git a/diffkemp/building/build_utils.py b/diffkemp/building/build_utils.py index 2c15da68a..2c5bcf164 100644 --- a/diffkemp/building/build_utils.py +++ b/diffkemp/building/build_utils.py @@ -1,4 +1,5 @@ from diffkemp.llvm_ir.source_tree import SourceNotFoundException +from diffkemp.llvm_ir.llvm_module import LlvmParam import sys import os @@ -41,9 +42,45 @@ def generate_from_function_list(snapshot, fun_list): snapshot.add_fun(symbol, llvm_mod) print(os.path.relpath(llvm_mod.llvm, snapshot.source_tree.source_dir)) + elif llvm_mod.has_global(symbol): + print() + if add_funcs_for_globvar(snapshot, symbol, LlvmParam(symbol)): + snapshot.list_kind = "glob_var" else: snapshot.add_fun(symbol, None) - print("not a function") + print("not a function nor a global variable") except SourceNotFoundException: print("source not found") snapshot.add_fun(symbol, None) + + +def add_funcs_for_globvar(snapshot, name, data, skip_fun=None): + functions_added = False + if not data: + return functions_added + + for module in \ + snapshot.source_tree.get_modules_using_symbol(data.name): + # For now, we only support the x86 architecture in kernel + if "/arch/" in module.llvm and \ + "/arch/x86/" not in module.llvm: + continue + + curr_mod_path = os.path.relpath(module.llvm, + snapshot.source_tree.source_dir) + for func in module.get_functions_using_param(data): + if skip_fun is not None and func == skip_fun: + continue + + functions_added = True + snapshot.add_fun( + name=func, + llvm_mod=module, + glob_var=data.name, + tag="using global variable \"{}\"".format(data.name), + group=name) + print(" {}: {} (using global variable \"{}\")".format( + func, + curr_mod_path, + data.name)) + return functions_added diff --git a/diffkemp/compare.py b/diffkemp/compare.py index 791d3eac5..64db77f3b 100644 --- a/diffkemp/compare.py +++ b/diffkemp/compare.py @@ -33,7 +33,7 @@ def compare(args): class GroupInfo: def __init__(self, group, group_name, enable_module_cache, config, - output_dir): + output_dir, group_kind): self.group = group self.group_name = group_name self.group_dir = self._get_group_dir(output_dir) @@ -44,6 +44,7 @@ def __init__(self, group, group_name, enable_module_cache, config, self.cache = SimpLLCache(mkdtemp()) self.module_cache = {} self.group_result = None + self.group_kind = group_kind def _get_group_dir(self, output_dir): if output_dir is not None and self.group_name is not None: @@ -92,7 +93,8 @@ def __init__(self, cmd_args): self.result = Result(Result.Kind.NONE, cmd_args.snapshot_dir_old, cmd_args.snapshot_dir_new, start_time=default_timer(), - hierarchy_level=Result.HierarchyLevel.OVERALL) + hierarchy=Result.Hierarchy( + Result.Hierarchy.Level.OVERALL)) self.regex_pattern = re.compile(cmd_args.regex_filter) \ if cmd_args.regex_filter else None self.output_dir = None @@ -130,11 +132,15 @@ def _default_output_dir(self): return base_dirname def _compare_snapshots(self): + # group_name could be None or str, which are uncomparable for group_name, group in sorted(self.config.snapshot_first - .fun_groups.items()): + .fun_groups.items(), + key=lambda item: (item[0] is not None, + item[0])): group_info = GroupInfo(group, group_name, self.args.enable_module_cache, - self.config, self.output_dir) + self.config, self.output_dir, + self.config.snapshot_first.list_kind) self._compare_groups(group_info) self._finalize_output() @@ -149,7 +155,9 @@ def _compare_groups(self, group_info): Result.Kind.NONE, group_name, group_name, - hierarchy_level=Result.HierarchyLevel.GROUP) + hierarchy=Result.Hierarchy( + Result.Hierarchy.Level.GROUP, + group_info.group_kind)) else: group_info.group_result = self.result diff --git a/diffkemp/output.py b/diffkemp/output.py index b99b4ee22..c28526b57 100644 --- a/diffkemp/output.py +++ b/diffkemp/output.py @@ -35,7 +35,7 @@ def _create_output(self): def _create_results(self, current_result, name=None): """:param current_result: node in the result class tree""" - if current_result.hierarchy_level == Result.HierarchyLevel.FUNCTION: + if current_result.hierarchy.level == Result.Hierarchy.Level.FUNCTION: return self._create_function_result(current_result, name) inner_results = [] @@ -44,11 +44,11 @@ def _create_results(self, current_result, name=None): if result is not None: inner_results.append(result) - if current_result.hierarchy_level == Result.HierarchyLevel.OVERALL: + if current_result.hierarchy.level == Result.Hierarchy.Level.OVERALL: return inner_results # hierarchy_level == GROUP return { - "sysctl": name, # sysctl is the only usecase of groups + current_result.hierarchy.group_kind: name, "results": inner_results } diff --git a/diffkemp/semdiff/result.py b/diffkemp/semdiff/result.py index a02751461..ebba30b91 100644 --- a/diffkemp/semdiff/result.py +++ b/diffkemp/semdiff/result.py @@ -183,11 +183,16 @@ def __init__(self, name, filename=None, line=None, callstack=None, self.diff_kind = diff_kind self.covered = covered - class HierarchyLevel(IntEnum): + class Hierarchy: """Describes position of result instance in result comparison tree""" - OVERALL = 0 - GROUP = 1 - FUNCTION = 2 + class Level(IntEnum): + OVERALL = 0 + GROUP = 1 + FUNCTION = 2 + + def __init__(self, level, group_kind=None): + self.level = level + self.group_kind = group_kind class SymbolStat: def __init__(self): @@ -199,7 +204,8 @@ def __init__(self): self.empty_diff = 0 def __init__(self, kind, first_name, second_name, start_time=None, - stop_time=None, hierarchy_level=HierarchyLevel.FUNCTION): + stop_time=None, + hierarchy=Hierarchy(Hierarchy.Level.FUNCTION)): self.kind = kind self.first = Result.Entity(first_name) self.second = Result.Entity(second_name) @@ -209,7 +215,7 @@ def __init__(self, kind, first_name, second_name, start_time=None, self.inner = dict() self.start_time = start_time self.stop_time = stop_time - self.hierarchy_level = hierarchy_level + self.hierarchy = hierarchy def __str__(self): return str(self.kind) @@ -264,7 +270,7 @@ def report_symbol_stat(self, show_errors=False): def print_function_names(self, kind): for name, result in self.inner.items(): - if result.hierarchy_level == Result.HierarchyLevel.FUNCTION: + if result.hierarchy.level == Result.Hierarchy.Level.FUNCTION: if result.kind == kind: print(name) else: @@ -273,7 +279,7 @@ def print_function_names(self, kind): def _populate_symbol_stat(self, stats): """Aggregate statistics about analysed symbols.""" for result in self.inner.values(): - if result.hierarchy_level == Result.HierarchyLevel.FUNCTION: + if result.hierarchy.level == Result.Hierarchy.Level.FUNCTION: stats.total += 1 stats.eq += result.kind == Result.Kind.EQUAL stats.neq += result.kind == Result.Kind.NOT_EQUAL @@ -306,7 +312,7 @@ def __hash__(self): return hash(self.res.first.name) def _populate_unique_diffs(result, unique_diffs): - if result.hierarchy_level == Result.HierarchyLevel.FUNCTION: + if result.hierarchy.level == Result.Hierarchy.Level.FUNCTION: for _, inner_res in result.inner.items(): if (inner_res.diff == "" and inner_res.first.covered): diff --git a/diffkemp/simpll/CMakeLists.txt b/diffkemp/simpll/CMakeLists.txt index e16cbba6d..3d0ab2726 100644 --- a/diffkemp/simpll/CMakeLists.txt +++ b/diffkemp/simpll/CMakeLists.txt @@ -34,11 +34,11 @@ execute_process(COMMAND llvm-config --system-libs OUTPUT_VARIABLE system_libs OUTPUT_STRIP_TRAILING_WHITESPACE) -# Try to find system-wide Z3 (e.g. local build) -find_package(Z3 CONFIG) +# Use our FindZ3.cmake (e.g. nix build) +find_package(Z3 MODULE) if (NOT ${Z3_FOUND}) - # Use our FindZ3.cmake (e.g. nix build) - find_package(Z3 REQUIRED MODULE) + # Try to find system-wide Z3 (e.g. local build) + find_package(Z3 REQUIRED CONFIG) endif() add_library(simpll-lib ${srcs} ${passes} ${library}) diff --git a/diffkemp/snapshot.py b/diffkemp/snapshot.py index 165a2e1d8..b63879c38 100644 --- a/diffkemp/snapshot.py +++ b/diffkemp/snapshot.py @@ -229,6 +229,9 @@ def _from_yaml(self, yaml_file): if "sysctl" in g: group = g["sysctl"] functions = g["functions"] + elif "glob_var" in g: + group = g["glob_var"] + functions = g["functions"] else: group = None functions = g diff --git a/tests/unit_tests/result_test.py b/tests/unit_tests/result_test.py index 07749a17b..3eb4acf35 100644 --- a/tests/unit_tests/result_test.py +++ b/tests/unit_tests/result_test.py @@ -123,7 +123,7 @@ def single_function_result(graph): @pytest.fixture def multi_functions_result(graph, single_function_result): result = Result(Result.Kind.NONE, "old-snapshot-path", "new-snapshot-path", - hierarchy_level=Result.HierarchyLevel.OVERALL) + hierarchy=Result.Hierarchy(Result.Hierarchy.Level.OVERALL)) result.add_inner(single_function_result) result.graph = graph yield result @@ -132,9 +132,10 @@ def multi_functions_result(graph, single_function_result): @pytest.fixture def sysctl_group_result(graph, single_function_result): result = Result(Result.Kind.NONE, "old-snapshot-path", "new-snapshot-path", - hierarchy_level=Result.HierarchyLevel.OVERALL) + hierarchy=Result.Hierarchy(Result.Hierarchy.Level.OVERALL)) group_result = Result(Result.Kind.NONE, "group_name", "group_name", - hierarchy_level=Result.HierarchyLevel.GROUP) + hierarchy=Result.Hierarchy( + Result.Hierarchy.Level.GROUP, "sysctl")) group_result.add_inner(single_function_result) result.add_inner(group_result) result.graph = graph @@ -144,11 +145,13 @@ def sysctl_group_result(graph, single_function_result): @pytest.fixture def multi_groups_result(graph, single_function_result): result = Result(Result.Kind.NONE, "old-snapshot-path", "new-snapshot-path", - hierarchy_level=Result.HierarchyLevel.OVERALL) + hierarchy=Result.Hierarchy(Result.Hierarchy.Level.OVERALL)) outer_group_result = Result(Result.Kind.NONE, "outer_name", "outer_name", - hierarchy_level=Result.HierarchyLevel.GROUP) + hierarchy=Result.Hierarchy( + Result.Hierarchy.Level.GROUP, "sysctl")) inner_group_result = Result(Result.Kind.NONE, "inner_name", "inner_name", - hierarchy_level=Result.HierarchyLevel.GROUP) + hierarchy=Result.Hierarchy( + Result.Hierarchy.Level.GROUP, "sysctl")) inner_group_result.add_inner(single_function_result) outer_group_result.add_inner(inner_group_result) result.add_inner(outer_group_result) diff --git a/tests/unit_tests/simpll/CMakeLists.txt b/tests/unit_tests/simpll/CMakeLists.txt index 4e50fdfdf..78ca3a687 100644 --- a/tests/unit_tests/simpll/CMakeLists.txt +++ b/tests/unit_tests/simpll/CMakeLists.txt @@ -21,11 +21,11 @@ execute_process(COMMAND llvm-config --system-libs OUTPUT_VARIABLE system_libs OUTPUT_STRIP_TRAILING_WHITESPACE) -# Try to find system-wide Z3 (e.g. local build) -find_package(Z3 CONFIG) +# Use our FindZ3.cmake (e.g. nix build) +find_package(Z3 MODULE) if (NOT ${Z3_FOUND}) - # Use our FindZ3.cmake (e.g. nix build) - find_package(Z3 REQUIRED MODULE) + # Try to find system-wide Z3 (e.g. local build) + find_package(Z3 REQUIRED CONFIG) endif() target_include_directories(simpllTests PRIVATE ${Z3_CXX_INCLUDE_DIRS}) diff --git a/view/src/Result.js b/view/src/Result.js index 1305b7bb3..902b2c4d2 100644 --- a/view/src/Result.js +++ b/view/src/Result.js @@ -36,16 +36,6 @@ export default class Result { /** Absolute path to new snapshot. */ newSnapshot; - /** - * Comparison results with the following structure: - * -results: - * -sysctl: name (this field exists only in case of sysctl comparison) - * results: - * -function: name - * -diffs - */ - results; - /** * Create Result from YAML file. * @param {Object} yaml YAML file describing result of diffkemp compare. @@ -54,56 +44,70 @@ export default class Result { this.definitions = yaml.definitions; this.oldSnapshot = yaml['old-snapshot']; this.newSnapshot = yaml['new-snapshot']; - if (yaml.results.length !== 0 && !('function' in yaml.results[0])) { - this.results = yaml.results; - } else { - this.results = [{ results: yaml.results }]; - } - // Sort differing functions of each compared function - this.results.forEach((groupResult) => { - groupResult.results.forEach((result) => { - result.diffs.sort((diffA, diffB) => compareFunctionNames(diffA.function, diffB.function)); + this.#sortDifferingFunctions(yaml); + this.#createAllCompAndDiffFuns(yaml); + } + + /** + * Sort differing functions of each compared function + */ + #sortDifferingFunctions(result) { + if ('diffs' in result) { + result.diffs.sort((diffA, diffB) => compareFunctionNames(diffA.function, diffB.function)); + } else { + result.results.forEach((subresult) => { + this.#sortDifferingFunctions(subresult); }); - }); - this.#createAllCompAndDiffFuns(); + } } /** * Creates allCompFuns and allDiffFuns fields. - * Expects that names of differing functions are sorted in the `results`. + * Expects that names of differing functions are sorted in the `result`. + * If one function is present in multiple groups, only the first occourrence + * is stored in the #allCompFuns Map. */ - #createAllCompAndDiffFuns() { + #createAllCompAndDiffFuns(result) { // Map(compFunName, Map(diffFunName, diff)) this.#allCompFuns = new Map(); // Map(diffFunName, [...compFunNames]) this.#allDiffFuns = new Map(); - // foreach group (sysctl or all functions) - this.results.forEach((groupResult) => { - // foreach result (compared function) - groupResult.results.forEach((result) => { - if (!this.#allCompFuns.has(result.function)) { - // Map(diffFunName, diff) - const diffFunsForComp = new Map(); - // for each diff (differing function) - result.diffs.forEach((diff) => { - diffFunsForComp.set(diff.function, diff); - if (this.#allDiffFuns.has(diff.function)) { - this.#allDiffFuns.get(diff.function).push(result.function); - } else { - this.#allDiffFuns.set(diff.function, [result.function]); - } - }); - this.#allCompFuns.set(result.function, diffFunsForComp); - } - }); - }); + // populate allCompFuns and allDiffFuns recursively + this.#compAndDiffFunsRec(result); // sort all compared functions this.#allCompFuns = new Map([...this.#allCompFuns] .sort((a, b) => compareFunctionNames(a[0], b[0]))); // sort keys (differing function names) in the map this.#allDiffFuns = new Map([...this.#allDiffFuns] .sort((a, b) => compareFunctionNames(a[0], b[0]))); + // sort compFunNames for each diffFunName + this.#allDiffFuns.forEach((compFunNames) => { + compFunNames.sort((funA, funB) => compareFunctionNames(funA, funB)); + }); + } + + #compAndDiffFunsRec(result) { + if (!('diffs' in result)) { + result.results.forEach((subresult) => { + this.#compAndDiffFunsRec(subresult); + }); + return; + } + if (!this.#allCompFuns.has(result.function)) { + // Map(diffFunName, diff) + const diffFunsForComp = new Map(); + // for each diff (differing function) + result.diffs.forEach((diff) => { + diffFunsForComp.set(diff.function, diff); + if (this.#allDiffFuns.has(diff.function)) { + this.#allDiffFuns.get(diff.function).push(result.function); + } else { + this.#allDiffFuns.set(diff.function, [result.function]); + } + }); + this.#allCompFuns.set(result.function, diffFunsForComp); + } } /** diff --git a/view/src/tests/Result.test.js b/view/src/tests/Result.test.js index 0af8f8c0a..d07df08fa 100644 --- a/view/src/tests/Result.test.js +++ b/view/src/tests/Result.test.js @@ -39,14 +39,77 @@ const yaml = { ], }; +const yamlGroups = { + results: [ + { + function: 'fun3', + diffs: [ + { + function: 'fun2', + 'old-callstack': [], + 'new-callstack': [], + }, + { + function: 'fun3', + 'old-callstack': [], + 'new-callstack': [], + }, + ], + }, + { + glob_var: 'glob1', + results: [ + { + function: 'fun1', + diffs: [ + { + function: 'fun2', + 'old-callstack': [], + 'new-callstack': [], + }, + { + function: 'fun1', + 'old-callstack': [], + 'new-callstack': [], + }, + ], + }, + ], + }, + { + multi_group: 'multi1', + results: [ + { + glob_var: 'glob2', + results: [ + { + function: 'fun4', + diffs: [ + { + function: 'fun4', + 'old-callstack': [], + 'new-callstack': [], + }, + ], + }, + ], + }, + ], + }, + ], +}; + let result = null; +let resultGroups = null; beforeAll(() => { result = new Result(yaml); + resultGroups = new Result(yamlGroups); }); test('getCompFuns should return sorted array of all compared functions', () => { expect(result.getCompFuns()).toEqual(['down_read', 'scsi_host_alloc']); + expect(resultGroups.getCompFuns()).toEqual(['fun1', 'fun3', 'fun4']); }); test('getCompFuns for differing function should return sorted array of compared functions which differs in specified function', () => { @@ -54,6 +117,10 @@ test('getCompFuns for differing function should return sorted array of compared 'down_read', 'scsi_host_alloc', ]); + expect(resultGroups.getCompFuns('fun2')).toEqual([ + 'fun1', + 'fun3', + ]); }); test('getDiffFuns should return sorted array of all differing functions', () => { @@ -62,6 +129,12 @@ test('getDiffFuns should return sorted array of all differing functions', () => 'down_read', 'scsi_host_alloc', ]); + expect(resultGroups.getDiffFuns()).toEqual([ + 'fun1', + 'fun2', + 'fun3', + 'fun4', + ]); }); test('getDiffFuns for compared function should return sorted array of function in which the compared function differs', () => { @@ -69,44 +142,66 @@ test('getDiffFuns for compared function should return sorted array of function i '__down_read', 'scsi_host_alloc', ]); + expect(resultGroups.getDiffFuns('fun1')).toEqual([ + 'fun1', + 'fun2', + ]); }); test('getNextCompName should return next compared function', () => { expect(result.getNextCompName('down_read')).toEqual('scsi_host_alloc'); + expect(resultGroups.getNextCompName('fun1')).toEqual('fun3'); }); test('getPrevCompName should return previous compared function', () => { expect(result.getPrevCompName('scsi_host_alloc')).toEqual('down_read'); + expect(resultGroups.getPrevCompName('fun4')).toEqual('fun3'); }); test('getNextDiffFunNameForComp should return next differing function for compared function', () => { expect( result.getNextDiffFunNameForComp('down_read', '__down_read'), ).toEqual('down_read'); + expect( + resultGroups.getNextDiffFunNameForComp('fun3', 'fun2'), + ).toEqual('fun3'); }); + test('getPrevDiffFunNameForComp should return previous differing function for compared function', () => { expect(result.getPrevDiffFunNameForComp('down_read', 'down_read')).toEqual( '__down_read', ); + expect(resultGroups.getPrevDiffFunNameForComp('fun1', 'fun2')).toEqual( + 'fun1', + ); }); + test('getDiff should return item from results diffs for specified compared and differing function', () => { expect(result.getDiff('down_read', 'down_read')).toBe( yaml.results[0].diffs[1], ); + expect(resultGroups.getDiff('fun4', 'fun4')).toBe( + yamlGroups.results[2].results[0].results[0].diffs[0], + ); }); test('isFirstCompFun should return true for first compared function', () => { expect(result.isFirstCompFun('down_read')).toBe(true); + expect(resultGroups.isFirstCompFun('fun1')).toBe(true); }); test('isLastCompFun should return true for last compared function', () => { expect(result.isLastCompFun('scsi_host_alloc')).toBe(true); + expect(resultGroups.isLastCompFun('fun4')).toBe(true); }); test('isFirstDiffFunForComp should return true for first differing function of compared function', () => { expect(result.isFirstDiffFunForComp('down_read', '__down_read')).toBe(true); + expect(resultGroups.isFirstDiffFunForComp('fun1', 'fun1')).toBe(true); }); test('isLastDiffFunForComp should return true for last differing function of compared function', () => { expect(result.isLastDiffFunForComp('down_read', 'down_read')).toBe(true); + expect(resultGroups.isLastDiffFunForComp('fun4', 'fun4')).toBe(true); }); test('getFirstDiffFunForComp should return first differing function for compared function', () => { expect(result.getFirstDiffFunForComp('down_read')).toEqual('__down_read'); + expect(resultGroups.getFirstDiffFunForComp('fun3')).toEqual('fun2'); });