diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-04-28 23:37:27 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-05-06 11:50:46 -0600 |
commit | 7259e7ffbebffa74c1c07257076bcb86fdf08689 (patch) | |
tree | a843fe047b337b8708cf9acd0806d1046d792f68 /build-aux/measurestack/test_app_plugins.py | |
parent | 8e21fcfc3b332e749135e4081dacb5556b30f5be (diff) |
measurestack: Fix+test skipmodel chain collections
Diffstat (limited to 'build-aux/measurestack/test_app_plugins.py')
-rw-r--r-- | build-aux/measurestack/test_app_plugins.py | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/build-aux/measurestack/test_app_plugins.py b/build-aux/measurestack/test_app_plugins.py new file mode 100644 index 0000000..abdccca --- /dev/null +++ b/build-aux/measurestack/test_app_plugins.py @@ -0,0 +1,138 @@ +# build-aux/measurestack/test_app_plugins.py - Tests for app_plugins.py +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +# pylint: disable=unused-variable + +import typing + +from . import analyze, app_plugins, util, vcg +from .analyze import BaseName, Node, QName, SkipModel + + +def aprime_gen(l: int, n: int) -> typing.Sequence[int]: + """Return an `l`-length sequence of nonnegative + integers such that any `n`-length-or-shorter combination of + members with repeats allowed can be uniquely identified by its + sum. + + (If that were "product" instead of "sum", the obvious solution + would be the first `l` primes.) + + """ + seq = [1] + while len(seq) < l: + x = seq[-1] * n + 1 + seq.append(x) + return seq + + +def aprime_decompose( + aprimes: typing.Sequence[int], tot: int +) -> tuple[typing.Collection[int], typing.Collection[int]]: + ret_idx = [] + ret_val = [] + while tot: + idx = max(i for i in range(len(aprimes)) if aprimes[i] <= tot) + val = aprimes[idx] + ret_idx.append(idx) + ret_val.append(val) + tot -= val + return ret_idx, ret_val + + +def aprime_assert( + aprimes: typing.Sequence[int], act_sum: int, exp_idxs: typing.Collection[int] +) -> None: + act_idxs, act_vals = aprime_decompose(aprimes, act_sum) + exp_sum = sum(aprimes[i] for i in exp_idxs) + # exp_vals = [aprimes[i] for i in exp] + + act_str = f"{act_sum}:{[f's[{v}]' for v in sorted(act_idxs)]}" + exp_str = f"{exp_sum}:{[f's[{v}]' for v in sorted(exp_idxs)]}" + if act_str != exp_str: + assert f"act={act_str}" == f"exp={exp_str}" + + +def test_assert_msg_fail() -> None: + num_funcs = 7 + max_call_depth = 7 + s = aprime_gen(num_funcs, max_call_depth) + + class TestApplication: + def extra_nodes(self) -> typing.Collection[Node]: + # 1 2 3 4 5 6 7 <= call_depth + # - main() s[0] + # - __assert_msg_fail() s[1] * + # - __lm_light_printf() s[3] + # - fmt_vfctprintf() s[6] + # - stdio_putchar() s[5] + # - __assert_msg_fail() s[1] ** + # - __lm_abort() s[2] + # - stdio_flush() s[4] (inconsequential) + # - __lm_abort() s[2] (inconsequential) + # ---- + # sum(s[i] for i in [0, 1, 3, 6, 5, 1, 2]) + ret = [ + # main.c + util.synthetic_node("main", s[0], {"__assert_msg_fail"}), + # assert.c + util.synthetic_node( + "__assert_msg_fail", s[1], {"__lm_light_printf", "__lm_abort"} + ), + # intercept.c / libfmt/libmisc.c + util.synthetic_node("__lm_abort", s[2]), + util.synthetic_node( + "__lm_light_printf", s[3], {"fmt_vfctprintf", "stdio_flush"} + ), + util.synthetic_node("stdio_flush", s[4]), + util.synthetic_node("stdio_putchar", s[5], {"__assert_msg_fail"}), + # printf.c + util.synthetic_node("fmt_vfctprintf", s[6], {"stdio_putchar"}), + ] + assert num_funcs == len(s) == len(ret) == len(set(n.nstatic for n in ret)) + return ret + + def indirect_callees( + self, elem: vcg.VCGElem + ) -> tuple[typing.Collection[QName], bool]: + return [], False + + def skipmodels(self) -> dict[BaseName, SkipModel]: + models = app_plugins.LibMiscPlugin(arg_c_fnames=[]).skipmodels() + assert BaseName("__assert_msg_fail") in models + orig_model = models[BaseName("__assert_msg_fail")] + + def wrapped_model_fn(chain: typing.Sequence[QName], call: QName) -> bool: + assert len(chain) > 1 + assert chain[-1] == QName("__assert_msg_fail") + assert ( + "=>".join(str(c) for c in chain) + == "__assert_msg_fail=>__lm_light_printf=>fmt_vfctprintf=>stdio_putchar=>__assert_msg_fail" + ) + assert call in [QName("__lm_light_printf"), QName("__lm_abort")] + return orig_model.fn(chain, call) + + models[BaseName("__assert_msg_fail")] = SkipModel( + orig_model.nchain, wrapped_model_fn + ) + return models + + def test_filter(name: QName) -> tuple[int, bool]: + if name.base() == BaseName("main"): + return 1, True + return 0, False + + result = analyze.analyze( + ci_fnames=[], + app_func_filters={ + "Main": test_filter, + }, + app=TestApplication(), + cfg_max_call_depth=max_call_depth, + ) + + aprime_assert( + s, result.groups["Main"].rows[QName("main")].nstatic, [0, 1, 3, 6, 5, 1, 2] + ) |