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.py379
1 files changed, 379 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..da8be65
--- /dev/null
+++ b/build-aux/measurestack/test_app_plugins.py
@@ -0,0 +1,379 @@
+# 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, testutil, util
+from .analyze import BaseName, Node, QName, SkipModel
+
+
+def test_assert_msg_fail() -> None:
+ # 1 2 3 4 5 6 7 <= call_depth
+ # - main()
+ # - __assert_msg_fail() *
+ # - __lm_light_printf()
+ # - fmt_vfctprintf()
+ # - stdio_putchar()
+ # - __assert_msg_fail() **
+ # - __lm_abort()
+ # - stdio_flush() (inconsequential)
+ # - __lm_abort() (inconsequential)
+ max_call_depth = 7
+ exp = [
+ "main",
+ "__assert_msg_fail",
+ "__lm_light_printf",
+ "fmt_vfctprintf",
+ "stdio_putchar",
+ "__assert_msg_fail",
+ "__lm_abort",
+ ]
+ graph: typing.Sequence[tuple[str, typing.Collection[str]]] = [
+ # main.c
+ ("main", {"__assert_msg_fail"}),
+ # assert.c
+ ("__assert_msg_fail", {"__lm_light_printf", "__lm_abort"}),
+ # intercept.c / libfmt/libmisc.c
+ ("__lm_abort", {}),
+ ("__lm_light_printf", {"fmt_vfctprintf", "stdio_flush"}),
+ ("stdio_flush", {}),
+ ("stdio_putchar", {"__assert_msg_fail"}),
+ # printf.c
+ ("fmt_vfctprintf", {"stdio_putchar"}),
+ ]
+ graph_plugin = testutil.GraphProviderPlugin(max_call_depth, graph)
+
+ class SkipPlugin(testutil.NopPlugin):
+ 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=util.PluginApplication(
+ testutil.nop_location_xform, [graph_plugin, SkipPlugin()]
+ ),
+ cfg_max_call_depth=max_call_depth,
+ )
+
+ graph_plugin.assert_nstatic(result.groups["Main"].rows[QName("main")].nstatic, exp)
+
+
+def test_fct() -> None:
+ # 1. | a + | b + | c + |*
+ # 2. | fmt_vsnprintf + | vprintf + | __lm_light_printf + |*
+ # 3. | fmt_vfctprintf + | fmt_vfctprintf + | fmt_vfctprintf + |
+ # 4. | fmt_state_putchar + | fmt_state_putchar + | fmt_state_putchar + |
+ # 5. | _out_buffer + | stdio_buffered_printer + | libfmt_light_fct + |*
+ # 6. | | __assert_msg_fail + | __assert_msg_fail + |
+ # 7. | | a. __lm_light_printf + | a. __lm_light_printf + |
+ # 8. | | a. fmt_vfctprintf + | a. fmt_vfctprintf + |
+ # 9. | | a. fmt_state_putchar + | a. fmt_state_putchar + |
+ # 10. | | a. libfmt_light_fct + | a. libfmt_light_fct + |
+ # 11. | | a. __assert_msg_fail + | a. __assert_msg_fail + |
+ # 12. | | a. __lm_abort + | a. __lm_abort + |
+ # 7. | | b. __lm_abort | b. __lm_abort |
+ max_call_depth = 12
+ exp_a = ["a", "fmt_vsnprintf", "fmt_vfctprintf", "fmt_state_putchar", "_out_buffer"]
+ exp_b = [
+ "b",
+ "__wrap_vprintf",
+ "fmt_vfctprintf",
+ "fmt_state_putchar",
+ "stdio_buffered_printer",
+ "__assert_msg_fail",
+ "__lm_light_printf",
+ "fmt_vfctprintf",
+ "fmt_state_putchar",
+ "libfmt_light_fct",
+ "__assert_msg_fail",
+ "__lm_abort",
+ ]
+ exp_c = [
+ "c",
+ "__lm_light_printf",
+ "fmt_vfctprintf",
+ "fmt_state_putchar",
+ "libfmt_light_fct",
+ "__assert_msg_fail",
+ "__lm_light_printf",
+ "fmt_vfctprintf",
+ "fmt_state_putchar",
+ "libfmt_light_fct",
+ "__assert_msg_fail",
+ "__lm_abort",
+ ]
+ graph: typing.Sequence[tuple[str, typing.Collection[str]]] = [
+ # main.c
+ ("a", {"fmt_vsnprintf"}), # _out_buffer
+ ("b", {"vprintf"}), # stdio_buffered_printer
+ ("c", {"__lm_light_printf"}), # libfmt_light_printf
+ # wrappers
+ ("fmt_vsnprintf", {"fmt_vfctprintf"}),
+ ("__wrap_vprintf", {"fmt_vfctprintf"}),
+ ("__lm_light_printf", {"fmt_vfctprintf"}),
+ # printf.c
+ ("fmt_vfctprintf", {"fmt_state_putchar"}),
+ (
+ "fmt_state_putchar",
+ {"_out_buffer", "stdio_buffered_printer", "libfmt_light_fct"},
+ ),
+ # fcts
+ ("_out_buffer", {}),
+ ("stdio_buffered_printer", {"__assert_msg_fail"}),
+ ("libfmt_light_fct", {"__assert_msg_fail"}),
+ # assert.c
+ ("__assert_msg_fail", {"__lm_light_printf", "__lm_abort"}),
+ # intercept.c / libfmt/libmisc.c
+ ("__lm_abort", {}),
+ ]
+ graph_plugin = testutil.GraphProviderPlugin(max_call_depth, graph)
+
+ plugins: list[util.Plugin] = [
+ graph_plugin,
+ 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
+
+ result = analyze.analyze(
+ ci_fnames=[],
+ app_func_filters={
+ "Main": test_filter,
+ },
+ app=util.PluginApplication(testutil.nop_location_xform, plugins),
+ cfg_max_call_depth=max_call_depth,
+ )
+
+ graph_plugin.assert_nstatic(result.groups["Main"].rows[QName("a")].nstatic, exp_a)
+ graph_plugin.assert_nstatic(result.groups["Main"].rows[QName("b")].nstatic, exp_b)
+ graph_plugin.assert_nstatic(result.groups["Main"].rows[QName("c")].nstatic, exp_c)
+
+
+def test_assert_formatter() -> None:
+ # _________________________________________________________
+ # | |
+ # | |
+ # | main |
+ # | | |
+ # | _ __wrap_vprintf |
+ # | / \ | _______________ |
+ # | | fmt_vfctprintf / \ |
+ # | | \ fmt_state_printf | |
+ # | | \____ ____/ | |
+ # | | \ / | |
+ # | | _vfctprintf | |
+ # | | ____/ \____ ^ |
+ # | | / ?<---snip | |
+ # | | conv_builtin \ | |
+ # | | | libfmt_conv_formatter | |
+ # | | | | | |
+ # | ^ \ lib9p_msg_Rread_format | |
+ # | | \ _____________/ | \______/ |
+ # | | \ / \ |
+ # | | fmt_state_putchar \ |
+ # | | ?<-?<--------snip | |
+ # | | / \_________ | |
+ # | | / \ | |
+ # | | stdio_buffered_printer \ | |
+ # | | \ libfmt_light_fct | |
+ # | | \ | / |
+ # | | \_______ | ________/ |
+ # | | \ | / |
+ # | | __assert_msg_fail |
+ # | | ___/ \____ |
+ # | | snip--->? \ |
+ # | | / \ |
+ # | | __lm_light_printf \ |
+ # | \____________/ __lm_abort |
+ # | |
+ # |_________________________________________________________|
+ #
+ graph: typing.Sequence[tuple[str, typing.Collection[str]]] = [
+ ("main", {"vprintf"}),
+ ("__wrap_vprintf", {"fmt_vfctprintf"}),
+ ("fmt_vfctprintf", {"_vfctprintf"}),
+ ("fmt_state_printf", {"_vfctprintf"}),
+ ("_vfctprintf", {"conv_builtin", "libfmt_conv_formatter"}),
+ ("conv_builtin", {"fmt_state_putchar"}),
+ ("libfmt_conv_formatter", {"lib9p_msg_Rread_format"}),
+ (
+ "lib9p_msg_Rread_format",
+ {"fmt_state_putchar", "__assert_msg_fail", "fmt_state_printf"},
+ ),
+ ("fmt_state_putchar", {"stdio_buffered_printer", "libfmt_light_fct"}),
+ ("stdio_buffered_printer", {"__assert_msg_fail"}),
+ ("libfmt_light_fct", {"__assert_msg_fail"}),
+ ("__assert_msg_fail", {"__lm_light_printf", "__lm_abort"}),
+ ("__lm_light_printf", {"fmt_vfctprintf"}),
+ ("__lm_abort", {}),
+ ]
+
+ # fct-determining wrappers have their callees marked with "|":
+ #
+ # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <= call_depth
+ # - main() ; +
+ # - __wrap__vprintf() ; +
+ # |- fmt_vfctprintf() ; +
+ # | - _vfctprintf() ; +
+ # | - conv_builtin() ;
+ # | - fmt_state_putchar() ;
+ # | - stdio_buffered_printer() ;
+ # | - __assert_msg_fail() ;
+ # | - __lm_light_printf() ;
+ # | |- fmt_vfctprintf() ;
+ # | | - _vfctprintf() ;
+ # | | - conv_builtin() ;
+ # | | - fmt_state_putchar() ;
+ # | | - stdio_buffered_printer() ; skip (wrong fct)
+ # | | - libfmt_light_fct() ;
+ # | | - __assert_msg_fail() ;
+ # | | - __lm_light_printf() ; skip (nested __assert_msg_fail)
+ # | | - __lm_abort() ;
+ # | | - libfmt_conv_formatter() ; skip (fct won't use %v)
+ # | - __lm_abort() ;
+ # | - libfmt_light_fct() ; skip (wrong fct)
+ # | - libfmt_conv_formatter() ; +
+ # | - lib9p_msg_Rread_format() ; +
+ # | - fmt_state_putchar() ;
+ # | - stdio_buffered_printer() ;
+ # | - __assert_msg_fail() ;
+ # | - __lm_light_printf() ;
+ # | |- fmt_vfctprintf() ;
+ # | | - _vfctprintf() ;
+ # | | - conv_builtin() ;
+ # | | - fmt_state_putchar() ;
+ # | | - stdio_buffered_printer() ; skip (wrong fct)
+ # | | - libfmt_light_fct() ;
+ # | | - __assert_msg_fail() ;
+ # | | - __lm_light_printf() ; skip (neseted __assert_msg_fail)
+ # | | - __lm_abort() ;
+ # | | - libfmt_conv_formatter() ; skip (fct won't use %v)
+ # | - __lm_abort() ;
+ # | - libfmt_light_fct() ; skip (wrong fct)
+ # | - __assert_msg_fail() ;
+ # | - __lm_light_printf() ;
+ # | |- fmt_vfctprintf() ;
+ # | | - _vfctprintf() ;
+ # | | - conv_builtin() ;
+ # | | - fmt_state_putchar() ;
+ # | | - stdio_buffered_printer() ; skip (wrong fct)
+ # | | - libfmt_light_fct() ;
+ # | | - __assert_msg_fail() ;
+ # | | - __lm_light_printf() ; skip (nested__assert_msg_fail)
+ # | | - __lm_abort() ;
+ # | | - libfmt_conv_formatter() ; skip (formatter won't use %v)
+ # | - __lm_abort() ;
+ # | - fmt_state_printf() ; +
+ # | - _vfctprintf() ; +
+ # | - conv_builtin() ; +
+ # | - fmt_state_putchar() ; +
+ # | - stdio_buffered_printer() ; +
+ # | - __assert_msg_fail() ; +
+ # | - __lm_light_printf() ; +
+ # | |- fmt_vfctprintf() ; +
+ # | | - _vfctprintf() ; +
+ # | | - conv_builtin() ; +
+ # | | - fmt_state_putchar() ; +
+ # | | - stdio_buffered_printer() ; skip (wrong fct)
+ # | | - libfmt_light_fct() ; +
+ # | | - __assert_msg_fail() ; +
+ # | | - __lm_light_printf() ; skip (neseted __assert_msg_fail)
+ # | | - __lm_abort() ; +
+ # | | - libfmt_conv_formatter() ; skip (fct won't use %v)
+ # | - __lm_abort() ;
+ # | - libfmt_light_fct() ; skip (wrong fct)
+ # | - libfmt_conv_formatter() ; skip (formatter won't use %v)
+ max_call_depth = 20
+ exp = [
+ "main",
+ "__wrap_vprintf",
+ "fmt_vfctprintf",
+ "_vfctprintf",
+ "libfmt_conv_formatter",
+ "lib9p_msg_Rread_format",
+ "fmt_state_printf",
+ "_vfctprintf",
+ "conv_builtin",
+ "fmt_state_putchar",
+ "stdio_buffered_printer",
+ "__assert_msg_fail",
+ "__lm_light_printf",
+ "fmt_vfctprintf",
+ "_vfctprintf",
+ "conv_builtin",
+ "fmt_state_putchar",
+ "libfmt_light_fct",
+ "__assert_msg_fail",
+ "__lm_abort",
+ ]
+
+ graph_plugin = testutil.GraphProviderPlugin(max_call_depth, graph)
+
+ plugins: list[util.Plugin] = [
+ graph_plugin,
+ app_plugins.LibMiscPlugin(arg_c_fnames=[]),
+ app_plugins.PicoFmtPlugin("rp2040", [BaseName("lib9p_msg_Rread_format")]),
+ ]
+
+ 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=util.PluginApplication(testutil.nop_location_xform, plugins),
+ cfg_max_call_depth=max_call_depth,
+ )
+
+ graph_plugin.assert_nstatic(result.groups["Main"].rows[QName("main")].nstatic, exp)