Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@v10
- uses: DeterminateSystems/nix-installer-action@v21
- name: Build using nix
run: nix build
- name: Run built Diffkemp
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@v10
- uses: DeterminateSystems/nix-installer-action@v21
- name: Build DiffKemp
run: >
nix develop . --command bash -c "
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@v10
- uses: DeterminateSystems/nix-installer-action@v21

- name: Delete unused software
# This is necessary for running CScope database build as it takes
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/code-style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@v10
- uses: DeterminateSystems/nix-installer-action@v21

- name: Check Coding Style
working-directory: ${{ github.workspace }}
Expand Down
26 changes: 22 additions & 4 deletions diffkemp/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(self, group, group_name, enable_module_cache, config,
self.result_graph = None
self.cache = SimpLLCache(mkdtemp())
self.module_cache = {}
self.group_result = None

def _get_group_dir(self, output_dir):
if output_dir is not None and self.group_name is not None:
Expand Down Expand Up @@ -90,7 +91,8 @@ def __init__(self, cmd_args):
self.config = Config.from_args(cmd_args)
self.result = Result(Result.Kind.NONE, cmd_args.snapshot_dir_old,
cmd_args.snapshot_dir_new,
start_time=default_timer())
start_time=default_timer(),
hierarchy_level=Result.HierarchyLevel.OVERALL)
self.regex_pattern = re.compile(cmd_args.regex_filter) \
if cmd_args.regex_filter else None
self.output_dir = None
Expand Down Expand Up @@ -139,9 +141,26 @@ def _compare_snapshots(self):
return 0

def _compare_groups(self, group_info):
group_name = group_info.group_name
# Checks if there are groups (e.g. for sysctl parameters),
# creates group_result to be able to connect results in the group
if group_name is not None:
group_info.group_result = Result(
Result.Kind.NONE,
group_name,
group_name,
hierarchy_level=Result.HierarchyLevel.GROUP)
else:
group_info.group_result = self.result

for fun, old_fun_desc in sorted(group_info.group.functions.items()):
self._compare_function(fun, old_fun_desc, group_info)

# add_inner method modifies result.graph so it has to be called when
# group_result.graph is already updated
if group_name is not None:
self.result.add_inner(group_info.group_result)

def _compare_function(self, fun, old_fun_desc, group_info):
# Check if the function exists in the other snapshot
new_fun_desc = \
Expand Down Expand Up @@ -179,16 +198,15 @@ def _modules_exist(old_fun_desc, new_fun_desc):

def _handle_missing_module(self, fun, old_fun_desc, group_info):
fun_result = Result(Result.Kind.UNKNOWN, fun, fun)
self.result.add_inner(fun_result)
group_info.group_result.add_inner(fun_result)
self._print_fun_result(fun_result, fun, old_fun_desc, group_info)

def _handle_fun_result(self, fun_result, fun, old_fun_desc, group_info):

if self.args.regex_filter is not None:
# Filter results by regex
self._filter_result_by_regex(fun_result)

self.result.add_inner(fun_result)
group_info.group_result.add_inner(fun_result)

# Printing information about failures and non-equal functions.
if fun_result.kind in [Result.Kind.NOT_EQUAL,
Expand Down
97 changes: 57 additions & 40 deletions diffkemp/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,49 +30,66 @@ def _create_output(self):
"""Creates yaml representation of compare result."""
self.output["old-snapshot"] = self.old_dir
self.output["new-snapshot"] = self.new_dir

self._create_results()
self.output["results"] = self._create_results(self.result)
self._create_definitions()

def _create_results(self):
"""Creates call stacks of not-equal functions."""
# list of all not-equal functions callstacks
results = []
for fun_name, fun_result in self.result.inner.items():
if fun_result.kind != Result.Kind.NOT_EQUAL:
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:
return self._create_function_result(current_result, name)

inner_results = []
for inner_name, inner_result in current_result.inner.items():
result = self._create_results(inner_result, inner_name)
if result is not None:
inner_results.append(result)

if current_result.hierarchy_level == Result.HierarchyLevel.OVERALL:
return inner_results
# hierarchy_level == GROUP
return {
"sysctl": name, # sysctl is the only usecase of groups
"results": inner_results
}

def _create_function_result(self, fun_result, fun_name):
"""
Creates call stacks of not-equal functions.
:param fun_result: output of snapshot comparison
"""
if fun_result.kind != Result.Kind.NOT_EQUAL:
return
self.function_names.add(fun_name)
# list of not-equal functions callstacks for a function
diffs = []
for called_res in sorted(fun_result.inner.values(),
key=lambda r: r.first.name):
if called_res.diff == "" and called_res.first.covered:
# Do not add empty diffs
continue
self.function_names.add(fun_name)
# list of not-equal functions callstacks for a function
diffs = []
for called_res in sorted(fun_result.inner.values(),
key=lambda r: r.first.name):
if called_res.diff == "" and called_res.first.covered:
# Do not add empty diffs
continue
# information about a function which is different
diff = {}
diff["function"] = called_res.first.name
# getting callstacks
diff["old-callstack"] = called_res.first \
.callstack.to_output_yaml_with_rel_path(self.old_dir) \
if called_res.first.callstack else []
diff["new-callstack"] = called_res.second \
.callstack.to_output_yaml_with_rel_path(self.new_dir) \
if called_res.second.callstack else []
# updating symbol names
if called_res.first.callstack:
function_names, macro_names, type_names = called_res \
.first.callstack.get_symbol_names(fun_name)
self.function_names.update(function_names)
self.macro_names.update(macro_names)
self.type_names.update(type_names)

diffs.append(diff)
results.append({
"function": fun_name,
"diffs": diffs
})
self.output["results"] = results
# information about a function which is different
diff = {}
diff["function"] = called_res.first.name
# getting callstacks
diff["old-callstack"] = called_res.first \
.callstack.to_output_yaml_with_rel_path(self.old_dir) \
if called_res.first.callstack else []
diff["new-callstack"] = called_res.second \
.callstack.to_output_yaml_with_rel_path(self.new_dir) \
if called_res.second.callstack else []
# updating symbol names
if called_res.first.callstack:
function_names, macro_names, type_names = called_res \
.first.callstack.get_symbol_names(fun_name)
self.function_names.update(function_names)
self.macro_names.update(macro_names)
self.type_names.update(type_names)

diffs.append(diff)
return {
"function": fun_name,
"diffs": diffs
}

def _create_definitions(self):
self.output["definitions"] = {}
Expand Down
107 changes: 71 additions & 36 deletions diffkemp/semdiff/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,23 @@ def __init__(self, name, filename=None, line=None, callstack=None,
self.diff_kind = diff_kind
self.covered = covered

class HierarchyLevel(IntEnum):
"""Describes position of result instance in result comparison tree"""
OVERALL = 0
GROUP = 1
FUNCTION = 2

class SymbolStat:
def __init__(self):
self.total = 0
self.eq = 0
self.neq = 0
self.unkwn = 0
self.errs = 0
self.empty_diff = 0

def __init__(self, kind, first_name, second_name, start_time=None,
stop_time=None):
stop_time=None, hierarchy_level=HierarchyLevel.FUNCTION):
self.kind = kind
self.first = Result.Entity(first_name)
self.second = Result.Entity(second_name)
Expand All @@ -194,6 +209,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

def __str__(self):
return str(self.kind)
Expand All @@ -220,43 +236,56 @@ def report_symbol_stat(self, show_errors=False):
Print numbers of equal, non-equal, unknown, and error results with
percentage that each has from the total results.
"""
total = len(self.inner)
eq = len([r for r in iter(self.inner.values())
if r.kind == Result.Kind.EQUAL])
neq = len([r for r in iter(self.inner.values())
if r.kind == Result.Kind.NOT_EQUAL])
unkwn = len([r for r in iter(self.inner.values())
if r.kind == Result.Kind.UNKNOWN])
errs = len([r for r in iter(self.inner.values())
if r.kind in [Result.Kind.ERROR, Result.Kind.TIMEOUT]])
empty_diff = len([r for r in iter(self.inner.values()) if all(map(
lambda x: x.diff == "", r.inner.values())) and
r.kind == Result.Kind.NOT_EQUAL])
if total > 0:
print("Total symbols: {}".format(total))
print("Equal: {0} ({1:.0f}%)".format(eq, eq / total * 100))
stats = Result.SymbolStat()
self._populate_symbol_stat(stats)

if stats.total > 0:
print("Total symbols: {}".format(stats.total))
print("Equal: {0} ({1:.0f}%)".format(
stats.eq, stats.eq / stats.total * 100))
print("Not equal: {0} ({1:.0f}%)".format(
neq, neq / total * 100))
stats.neq, stats.neq / stats.total * 100))
print("(empty diff): {0} ({1:.0f}%)".format(
empty_diff, empty_diff / total * 100))
print("Unknown: {0} ({1:.0f}%)".format(unkwn,
unkwn / total * 100))
print("Errors: {0} ({1:.0f}%)".format(errs,
errs / total * 100))
stats.empty_diff, stats.empty_diff / stats.total * 100))
print("Unknown: {0} ({1:.0f}%)".format(
stats.unkwn, stats.unkwn / stats.total * 100))
print("Errors: {0} ({1:.0f}%)".format(
stats.errs, stats.errs / stats.total * 100))

if show_errors:
if unkwn > 0:
if stats.unkwn > 0:
print("\nFunctions that are unknown: ")
for f, r in sorted(self.inner.items()):
if r.kind == Result.Kind.UNKNOWN:
print(f)
self.print_function_names(Result.Kind.UNKNOWN)
print()
if errs > 0:
if stats.errs > 0:
print("\nFunctions whose comparison ended with an error: ")
for f, r in sorted(self.inner.items()):
if r.kind == Result.Kind.ERROR:
print(f)
self.print_function_names(Result.Kind.ERROR)
print()

def print_function_names(self, kind):
for name, result in self.inner.items():
if result.hierarchy_level == Result.HierarchyLevel.FUNCTION:
if result.kind == kind:
print(name)
else:
result.print_function_names(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:
stats.total += 1
stats.eq += result.kind == Result.Kind.EQUAL
stats.neq += result.kind == Result.Kind.NOT_EQUAL
stats.unkwn += result.kind == Result.Kind.UNKNOWN
stats.errs += result.kind in [Result.Kind.ERROR,
Result.Kind.TIMEOUT]
stats.empty_diff += (all(map(lambda x: x.diff == "",
result.inner.values())) and
result.kind == Result.Kind.NOT_EQUAL)
else:
result._populate_symbol_stat(stats)

def report_object_stat(self):
"""
Report detailed statistics about compared objects.
Expand All @@ -276,14 +305,20 @@ def __eq__(self, other):
def __hash__(self):
return hash(self.res.first.name)

def _populate_unique_diffs(result, unique_diffs):
if result.hierarchy_level == Result.HierarchyLevel.FUNCTION:
for _, inner_res in result.inner.items():
if (inner_res.diff == "" and
inner_res.first.covered):
continue
unique_diffs.add(UniqueDiff(inner_res))
else:
for _, inner_result in result.inner.items():
_populate_unique_diffs(inner_result, unique_diffs)

# Convert inner result to set of unique diffs
unique_diffs = set()
for _, inner_res_out in self.inner.items():
for _, inner_res in inner_res_out.inner.items():
if (inner_res.diff == "" and
inner_res.first.covered):
continue
unique_diffs.add(UniqueDiff(inner_res))
_populate_unique_diffs(self, unique_diffs)

# Generate counts
compared = len(self.graph.vertices)
Expand Down
Loading