# build-aux/measurestack/test_app_plugins.py - Tests for app_plugins.py # # Copyright (C) 2025 Luke T. Shumaker # 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)