summaryrefslogtreecommitdiff
path: root/build-aux/measurestack/test_app_plugins.py
diff options
context:
space:
mode:
Diffstat (limited to 'build-aux/measurestack/test_app_plugins.py')
-rw-r--r--build-aux/measurestack/test_app_plugins.py259
1 files changed, 259 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..8aa0a6c
--- /dev/null
+++ b/build-aux/measurestack/test_app_plugins.py
@@ -0,0 +1,259 @@
+# 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], node: Node, call: QName
+ ) -> bool:
+ dbgstr = (
+ ("=>".join(str(c) for c in [*chain, node.funcname]))
+ + "=?=>"
+ + str(call)
+ )
+ assert dbgstr in [
+ "__assert_msg_fail=?=>__lm_light_printf",
+ "__assert_msg_fail=?=>__lm_abort",
+ "__assert_msg_fail=>__lm_light_printf=>fmt_vfctprintf=>stdio_putchar=>__assert_msg_fail=?=>__lm_light_printf",
+ "__assert_msg_fail=>__lm_light_printf=>fmt_vfctprintf=>stdio_putchar=>__assert_msg_fail=?=>__lm_abort",
+ ]
+ return orig_model.fn(chain, node, 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]
+ )
+
+
+def test_fct() -> None:
+ num_funcs = 13
+ max_call_depth = 12
+ s = aprime_gen(num_funcs, max_call_depth)
+
+ class TestPlugin:
+ def is_intrhandler(self, name: QName) -> bool:
+ return False
+
+ def init_array(self) -> typing.Collection[QName]:
+ return []
+
+ def extra_includes(self) -> typing.Collection[BaseName]:
+ return []
+
+ def indirect_callees(
+ self, loc: str, line: str
+ ) -> tuple[typing.Collection[QName], bool] | None:
+ return None
+
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
+
+ def extra_nodes(self) -> typing.Collection[Node]:
+ # 1. | a +s[0] | b +s[ 1] | c +s[ 2] |*
+ # 2. | fmt_vsnprintf +s[3] | vprintf +s[ 4] | __lm_light_printf +s[ 5] |*
+ # 3. | fmt_vfctprintf +s[6] | fmt_vfctprintf +s[ 6] | fmt_vfctprintf +s[ 6] |
+ # 4. | fmt_state_putchar +s[7] | fmt_state_putchar +s[ 7] | fmt_state_putchar +s[ 7] |
+ # 5. | _out_buffer +s[8] | stdio_buffered_printer +s[ 9] | libfmt_light_fct +s[10] |*
+ # 6. | | __assert_msg_fail +s[11] | __assert_msg_fail +s[11] |
+ # 7. | | a. __lm_light_printf +s[ 5] | a. __lm_light_printf +s[ 5] |
+ # 8. | | a. fmt_vfctprintf +s[ 6] | a. fmt_vfctprintf +s[ 6] |
+ # 9. | | a. fmt_state_putchar +s[ 7] | a. fmt_state_putchar +s[ 7] |
+ # 10. | | a. libfmt_light_fct +s[10] | a. libfmt_light_fct +s[10] |
+ # 11. | | a. __assert_msg_fail +s[11] | a. __assert_msg_fail +s[11] |
+ # 12. | | a. __lm_abort +s[12] | a. __lm_abort +s[12] |
+ # 7. | | b. __lm_abort | b. __lm_abort |
+ return [
+ # main.c
+ util.synthetic_node("a", s[0], {"fmt_vsnprintf"}), # _out_buffer
+ util.synthetic_node("b", s[1], {"vprintf"}), # stdio_buffered_printer
+ util.synthetic_node(
+ "c", s[2], {"__lm_light_printf"}
+ ), # libfmt_light_printf
+ # wrappers
+ util.synthetic_node("fmt_vsnprintf", s[3], {"fmt_vfctprintf"}),
+ util.synthetic_node("__wrap_vprintf", s[4], {"fmt_vfctprintf"}),
+ util.synthetic_node("__lm_light_printf", s[5], {"fmt_vfctprintf"}),
+ # printf.c
+ util.synthetic_node("fmt_vfctprintf", s[6], {"fmt_state_putchar"}),
+ util.synthetic_node(
+ "fmt_state_putchar",
+ s[7],
+ {"_out_buffer", "stdio_buffered_printer", "libfmt_light_fct"},
+ ),
+ # fcts
+ util.synthetic_node("_out_buffer", s[8]),
+ util.synthetic_node(
+ "stdio_buffered_printer", s[9], {"__assert_msg_fail"}
+ ),
+ util.synthetic_node("libfmt_light_fct", s[10], {"__assert_msg_fail"}),
+ # assert.c
+ util.synthetic_node(
+ "__assert_msg_fail",
+ s[11],
+ {"__lm_light_printf", "__lm_abort"},
+ ),
+ # intercept.c / libfmt/libmisc.c
+ util.synthetic_node("__lm_abort", s[12]),
+ ]
+
+ plugins: list[util.Plugin] = [
+ TestPlugin(),
+ app_plugins.LibMiscPlugin(arg_c_fnames=[]),
+ # fmt_vsnprintf => fct=_out_buffer
+ # if rp2040:
+ # __wrap_vprintf => fct=stdio_buffered_printer
+ # stdio_vprintf => fct=stdio_buffered_printer
+ # __lm_light_printf => fct=libfmt_light_fct
+ # if host:
+ # __lm_printf => fct=libfmt_libc_fct
+ # __lm_light_printf => fct=libfmt_libc_fct
+ app_plugins.PicoFmtPlugin("rp2040"),
+ ]
+
+ def test_filter(name: QName) -> tuple[int, bool]:
+ if str(name.base()) in ["a", "b", "c"]:
+ return 1, True
+ return 0, False
+
+ def _str_location_xform(loc: str) -> str:
+ return loc
+
+ result = analyze.analyze(
+ ci_fnames=[],
+ app_func_filters={
+ "Main": test_filter,
+ },
+ app=util.PluginApplication(_str_location_xform, plugins),
+ cfg_max_call_depth=max_call_depth,
+ )
+
+ aprime_assert(s, result.groups["Main"].rows[QName("a")].nstatic, [0, 3, 6, 7, 8])
+ aprime_assert(
+ s,
+ result.groups["Main"].rows[QName("b")].nstatic,
+ [1, 4, 6, 7, 9, 11, 5, 6, 7, 10, 11, 12],
+ )
+ aprime_assert(
+ s,
+ result.groups["Main"].rows[QName("c")].nstatic,
+ [2, 5, 6, 7, 10, 11, 5, 6, 7, 10, 11, 12],
+ )