From be7a0efac25f186cd0b5ad49e9d001e99d025b74 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Wed, 14 May 2025 15:43:31 -0600 Subject: measurestack: Woah, stdio won't assert(0)???? --- build-aux/measurestack/app_plugins.py | 15 +- build-aux/measurestack/test_app_plugins.py | 300 ++++++----------------------- 2 files changed, 55 insertions(+), 260 deletions(-) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index e365f82..1b10b67 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -129,20 +129,7 @@ class LibMiscPlugin: return None def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: - return { - BaseName("__assert_msg_fail"): analyze.SkipModel( - {BaseName("__assert_msg_fail")}, self._skipmodel___assert_msg_fail - ), - } - - def _skipmodel___assert_msg_fail( - self, chain: typing.Sequence[QName], node: Node, call: QName - ) -> bool: - if call.base() in [BaseName("__lm_printf"), BaseName("__lm_light_printf")]: - return any( - c.base() == BaseName("__assert_msg_fail") for c in reversed(chain) - ) - return False + return {} class LibHWPlugin: diff --git a/build-aux/measurestack/test_app_plugins.py b/build-aux/measurestack/test_app_plugins.py index da8be65..8a29a12 100644 --- a/build-aux/measurestack/test_app_plugins.py +++ b/build-aux/measurestack/test_app_plugins.py @@ -8,106 +8,16 @@ 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) +from .analyze import BaseName, QName def test_fct() -> None: - # 1. | a + | b + | c + |* - # 2. | fmt_vsnprintf + | vprintf + | __lm_light_printf + |* + # 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 + # 5. | _out_buffer + | stdio_buffered_printer + | libfmt_light_fct + | + max_call_depth = 5 exp_a = ["a", "fmt_vsnprintf", "fmt_vfctprintf", "fmt_state_putchar", "_out_buffer"] exp_b = [ "b", @@ -115,13 +25,6 @@ def test_fct() -> None: "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", @@ -129,13 +32,6 @@ def test_fct() -> None: "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 @@ -154,12 +50,8 @@ def test_fct() -> None: ), # 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", {}), + ("stdio_buffered_printer", {}), + ("libfmt_light_fct", {}), ] graph_plugin = testutil.GraphProviderPlugin(max_call_depth, graph) @@ -197,43 +89,31 @@ def test_fct() -> None: 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 | - # | | - # |_________________________________________________________| + # _____________________________________________________ + # | | + # | | + # | main | + # | | | + # | __wrap_vprintf | + # | | _______________ | + # | fmt_vfctprintf / \ | + # | \ fmt_state_printf | | + # | \____ ____/ | | + # | \ / | | + # | _vfctprintf | | + # | ____/ \____ ^ | + # | / ?<---snip | | + # | conv_builtin \ | | + # | | libfmt_conv_formatter | | + # | | | | | + # | \ lib9p_msg_Rread_format | | + # | \___________ __/ \___________/ | + # | \ / | + # | fmt_state_putchar | + # | | | + # | stdio_buffered_printer | + # | | + # |_____________________________________________________| # graph: typing.Sequence[tuple[str, typing.Collection[str]]] = [ ("main", {"vprintf"}), @@ -243,94 +123,31 @@ def test_assert_formatter() -> None: ("_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"}, - ), + ("lib9p_msg_Rread_format", {"fmt_state_putchar", "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", {}), + ("stdio_buffered_printer", {}), + ("libfmt_light_fct", {}), # wrong fct ] - # 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 + # 1 2 3 4 5 6 7 8 9 10 11 <= call_depth + # - main() ; + + # - __wrap__vprintf() ; + + # - fmt_vfctprintf() ; + + # - _vfctprintf() ; + + # - conv_builtin() ; + # - fmt_state_putchar() ; + # - stdio_buffered_printer() ; + # - libfmt_conv_formatter() ; + + # - lib9p_msg_Rread_format() ; + + # - fmt_state_putchar() ; + # - stdio_buffered_printer() ; + # - fmt_state_printf() ; + + # - _vfctprintf() ; + + # - conv_builtin() ; + + # - fmt_state_putchar() ; + + # - stdio_buffered_printer() ; + + # - libfmt_conv_formatter() ; skip (formatter won't use %v) + max_call_depth = 11 exp = [ "main", "__wrap_vprintf", @@ -343,15 +160,6 @@ def test_assert_formatter() -> None: "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) -- cgit v1.2.3-2-g168b From a226a1adf5e07f4281fcf95d3a69b7964159a95a Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Thu, 15 May 2025 13:16:20 -0600 Subject: lib9p: srv.c: Simplify handler calling --- build-aux/measurestack/app_plugins.py | 21 --------------------- 1 file changed, 21 deletions(-) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index 1b10b67..f9dfe40 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -255,9 +255,6 @@ class LibCRIPCPlugin: class Lib9PPlugin: - re_tmessage_handler = re.compile( - r"^\s*\[LIB9P_TYP_T[^]]+\]\s*=\s*\(tmessage_handler\)\s*(?P\S+),\s*$" - ) re_lib9p_msg_entry = re.compile(r"^\s*_MSG_(?:[A-Z]+)\((?P\S+)\),$") re_lib9p_caller = re.compile( r"^lib9p_(?P[TR])msg_(?Pvalidate|unmarshal|marshal)$" @@ -266,7 +263,6 @@ class Lib9PPlugin: r"^(?Pvalidate|unmarshal|marshal)_(?P(?P[TR]).*)$" ) - tmessage_handlers: set[QName] | None lib9p_msgs: set[str] _CONFIG_9P_MAX_CONNS: int | None _CONFIG_9P_MAX_REQS: int | None @@ -323,16 +319,6 @@ class Lib9PPlugin: # Read sources ######################################################### - tmessage_handlers: set[QName] | None = None - if lib9p_srv_c_fname: - tmessage_handlers = set() - with open(lib9p_srv_c_fname, "r", encoding="utf-8") as fh: - for line in fh: - line = line.rstrip() - if m := self.re_tmessage_handler.fullmatch(line): - tmessage_handlers.add(QName(m.group("handler"))) - self.tmessage_handlers = tmessage_handlers - lib9p_msgs: set[str] = set() if lib9p_generated_c_fname: with open(lib9p_generated_c_fname, "r", encoding="utf-8") as fh: @@ -369,13 +355,6 @@ class Lib9PPlugin: ) -> tuple[typing.Collection[QName], bool] | None: if "/3rd-party/" in loc: return None - if ( - self.tmessage_handlers - and "/srv.c:" in loc - and "tmessage_handlers[typ](" in line - ): - # Functions for disabled protocol extensions will be missing. - return self.tmessage_handlers, True if self.lib9p_msgs and "/9p.c:" in loc: for meth in ["validate", "unmarshal", "marshal"]: if line.startswith(f"tentry.{meth}("): -- cgit v1.2.3-2-g168b From 2ae702af39d370cede1b32a4b507df75b3438ddb Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Mon, 12 May 2025 15:31:09 -0600 Subject: lib9p: Adjust _lib9p_{validate,unmarshal,marshal} to be macros, not functions --- build-aux/measurestack/app_plugins.py | 62 +++++++++++++---------------------- 1 file changed, 23 insertions(+), 39 deletions(-) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index f9dfe40..1fb0355 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -255,13 +255,7 @@ class LibCRIPCPlugin: class Lib9PPlugin: - re_lib9p_msg_entry = re.compile(r"^\s*_MSG_(?:[A-Z]+)\((?P\S+)\),$") - re_lib9p_caller = re.compile( - r"^lib9p_(?P[TR])msg_(?Pvalidate|unmarshal|marshal)$" - ) - re_lib9p_callee = re.compile( - r"^(?Pvalidate|unmarshal|marshal)_(?P(?P[TR]).*)$" - ) + re_lib9p_msg_entry = re.compile(r"^\s*_MSG\((?P\S+)\),$") lib9p_msgs: set[str] _CONFIG_9P_MAX_CONNS: int | None @@ -297,7 +291,7 @@ class Lib9PPlugin: ) lib9p_generated_c_fname = util.get_zero_or_one( - lambda fname: fname.endswith("lib9p/_core_generated.c"), arg_c_fnames + lambda fname: fname.endswith("lib9p/core_generated.c"), arg_c_fnames ) # Read config ########################################################## @@ -350,45 +344,35 @@ class Lib9PPlugin: def extra_nodes(self) -> typing.Collection[Node]: return [] + re_table_call = re.compile( + r"\s*_lib9p_(?Pvalidate|unmarshal|marshal)\(.*(?P[RT])msg.*\);\s*" + ) + re_print_call = re.compile(r".*lib9p_table_msg.*\.box_as_fmt_formatter\(.*") + def indirect_callees( self, loc: str, line: str ) -> tuple[typing.Collection[QName], bool] | None: if "/3rd-party/" in loc: return None - if self.lib9p_msgs and "/9p.c:" in loc: - for meth in ["validate", "unmarshal", "marshal"]: - if line.startswith(f"tentry.{meth}("): - # Functions for disabled protocol extensions will be missing. - return [QName(f"{meth}_{msg}") for msg in self.lib9p_msgs], True + if self.lib9p_msgs and "lib9p/core.c:" in loc: + if m := self.re_table_call.fullmatch(line): + meth = m.group("meth") + grp = m.group("grp") + # Functions for disabled protocol extensions will be missing. + return [ + QName(f"{meth}_{msg}") + for msg in self.lib9p_msgs + if msg.startswith(grp) + ], True + if self.re_print_call.fullmatch(line): + # Functions for disabled protocol extensions will be missing. + return [ + QName(f"lib9p_msg_{msg}_format") for msg in self.lib9p_msgs + ], True return None def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: - ret: dict[BaseName, analyze.SkipModel] = { - BaseName("_lib9p_validate"): analyze.SkipModel( - 1, - self._skipmodel__lib9p_validate_unmarshal_marshal, - ), - BaseName("_lib9p_unmarshal"): analyze.SkipModel( - 1, - self._skipmodel__lib9p_validate_unmarshal_marshal, - ), - BaseName("_lib9p_marshal"): analyze.SkipModel( - 1, - self._skipmodel__lib9p_validate_unmarshal_marshal, - ), - } - return ret - - def _skipmodel__lib9p_validate_unmarshal_marshal( - self, chain: typing.Sequence[QName], node: Node, call: QName - ) -> bool: - m_caller = self.re_lib9p_caller.fullmatch(str(chain[-1].base())) - assert m_caller - - m_callee = self.re_lib9p_callee.fullmatch(str(call.base())) - if not m_callee: - return False - return m_caller.group("grp") != m_callee.group("grp") + return {} class PicoFmtPlugin: -- cgit v1.2.3-2-g168b From 4f567b6127cb15ec64a93f9fd340aaff493680a3 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Thu, 15 May 2025 13:49:19 -0600 Subject: measurestack: Learn about flash_range_program_func --- build-aux/measurestack/app_plugins.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index 1fb0355..32b4e0d 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -554,6 +554,8 @@ class PicoSDKPlugin: return [QName("rom_func_lookup(ROM_FUNC_FLASH_RANGE_ERASE)")], False case "flash_flush_cache_func": return [QName("rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE)")], False + case "flash_range_program_func": + return [QName("rom_func_lookup(ROM_FUNC_FLASH_RANGE_PROGRAM)")], False case "rom_table_lookup": return [QName("rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)")], False if "/flash.c:" in loc and "boot2_copyout" in line: -- cgit v1.2.3-2-g168b From 535760a916ba380abe1240292e6da5167ffd7552 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Thu, 15 May 2025 13:53:00 -0600 Subject: measurestack: lib9p: Learn about srv->msglog --- build-aux/measurestack/app_plugins.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index 32b4e0d..c6e4512 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -369,6 +369,10 @@ class Lib9PPlugin: return [ QName(f"lib9p_msg_{msg}_format") for msg in self.lib9p_msgs ], True + if "lib9p/srv.c:" in loc: + if "srv->msglog(" in line: + # Actual ROMs shouldn't set this, and so will be missing on rp2040 builds. + return [QName("log_msg")], True return None def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: -- cgit v1.2.3-2-g168b From 22336fb34f464dc751f86ad552fa1e40f06921ed Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Tue, 13 May 2025 00:34:06 -0600 Subject: libmisc: Invent fmt.h to replace printf --- build-aux/measurestack/app_main.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_main.py b/build-aux/measurestack/app_main.py index f705876..f933967 100644 --- a/build-aux/measurestack/app_main.py +++ b/build-aux/measurestack/app_main.py @@ -94,6 +94,11 @@ def main( QName("fmt_vsnprintf"), ]: return 1, False + for prefix in ["fmt_print_", "_fmt_print_"]: + if str(name.base()).startswith(prefix): + return 1, False + if str(name.base()).endswith("_putb"): + return 1, False return 0, False extra_includes: list[BaseName] = [] -- cgit v1.2.3-2-g168b From 67cec6d2770aa14a13c89247612f16c628ebd54c Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Wed, 14 May 2025 12:20:02 -0600 Subject: libmisc: Remove uses of printf --- build-aux/measurestack/app_main.py | 1 - build-aux/measurestack/app_plugins.py | 3 --- build-aux/measurestack/test_app_plugins.py | 32 ++++++++---------------------- 3 files changed, 8 insertions(+), 28 deletions(-) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_main.py b/build-aux/measurestack/app_main.py index f933967..c5b5a79 100644 --- a/build-aux/measurestack/app_main.py +++ b/build-aux/measurestack/app_main.py @@ -89,7 +89,6 @@ def main( if name in [ QName("__assert_msg_fail"), QName("__lm_printf"), - QName("__lm_light_printf"), QName("fmt_vfctprintf"), QName("fmt_vsnprintf"), ]: diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index c6e4512..8009c0c 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -397,8 +397,6 @@ class PicoFmtPlugin: # pico_stdio BaseName("__wrap_vprintf"): BaseName("stdio_buffered_printer"), BaseName("stdio_vprintf"): BaseName("stdio_buffered_printer"), - # libfmt - BaseName("__lm_light_printf"): BaseName("libfmt_light_fct"), } ) case "host": @@ -406,7 +404,6 @@ class PicoFmtPlugin: { # libfmt BaseName("__lm_printf"): BaseName("libfmt_libc_fct"), - BaseName("__lm_light_printf"): BaseName("libfmt_libc_fct"), } ) self.wont_call_v = set([*self.known_fct.values(), *wont_call_v]) diff --git a/build-aux/measurestack/test_app_plugins.py b/build-aux/measurestack/test_app_plugins.py index 8a29a12..808d55a 100644 --- a/build-aux/measurestack/test_app_plugins.py +++ b/build-aux/measurestack/test_app_plugins.py @@ -12,11 +12,11 @@ from .analyze import BaseName, QName 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 + | + # 1. | a + | b + | + # 2. | fmt_vsnprintf + | vprintf + | + # 3. | fmt_vfctprintf + | fmt_vfctprintf + | + # 4. | fmt_state_putchar + | fmt_state_putchar + | + # 5. | _out_buffer + | stdio_buffered_printer + | max_call_depth = 5 exp_a = ["a", "fmt_vsnprintf", "fmt_vfctprintf", "fmt_state_putchar", "_out_buffer"] exp_b = [ @@ -26,32 +26,19 @@ def test_fct() -> None: "fmt_state_putchar", "stdio_buffered_printer", ] - exp_c = [ - "c", - "__lm_light_printf", - "fmt_vfctprintf", - "fmt_state_putchar", - "libfmt_light_fct", - ] 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"}, - ), + ("fmt_state_putchar", {"_out_buffer", "stdio_buffered_printer"}), # fcts ("_out_buffer", {}), ("stdio_buffered_printer", {}), - ("libfmt_light_fct", {}), ] graph_plugin = testutil.GraphProviderPlugin(max_call_depth, graph) @@ -62,10 +49,8 @@ def test_fct() -> None: # 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", []), ] @@ -85,7 +70,6 @@ def test_fct() -> None: 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: @@ -124,9 +108,9 @@ def test_assert_formatter() -> None: ("conv_builtin", {"fmt_state_putchar"}), ("libfmt_conv_formatter", {"lib9p_msg_Rread_format"}), ("lib9p_msg_Rread_format", {"fmt_state_putchar", "fmt_state_printf"}), - ("fmt_state_putchar", {"stdio_buffered_printer", "libfmt_light_fct"}), + ("fmt_state_putchar", {"stdio_buffered_printer", "_out_buffer"}), ("stdio_buffered_printer", {}), - ("libfmt_light_fct", {}), # wrong fct + ("_out_buffer", {}), # wrong fct ] # 1 2 3 4 5 6 7 8 9 10 11 <= call_depth -- cgit v1.2.3-2-g168b From 9c0338b1b4495457659157e1e9f47d422dcefc2e Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Wed, 14 May 2025 16:37:27 -0600 Subject: lib9p: Remove uses of printf --- build-aux/measurestack/app_main.py | 4 +- build-aux/measurestack/app_plugins.py | 14 +--- build-aux/measurestack/test_app_plugins.py | 101 +---------------------------- 3 files changed, 5 insertions(+), 114 deletions(-) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_main.py b/build-aux/measurestack/app_main.py index c5b5a79..69247ed 100644 --- a/build-aux/measurestack/app_main.py +++ b/build-aux/measurestack/app_main.py @@ -28,7 +28,7 @@ def main( # sbc-harness #################################################### libmisc_plugin = app_plugins.LibMiscPlugin(arg_c_fnames) - lib9p_plugin = app_plugins.Lib9PPlugin(arg_base_dir, arg_c_fnames, libmisc_plugin) + lib9p_plugin = app_plugins.Lib9PPlugin(arg_base_dir, arg_c_fnames) def sbc_is_thread(name: QName) -> int: if str(name).endswith("_cr") and name.base() != BaseName("lib9p_srv_read_cr"): @@ -48,7 +48,7 @@ def main( plugins += [ app_plugins.CmdPlugin(), libmisc_plugin, - app_plugins.PicoFmtPlugin(arg_pico_platform, lib9p_plugin.formatters), + app_plugins.PicoFmtPlugin(arg_pico_platform, []), app_plugins.LibHWPlugin(arg_pico_platform, libmisc_plugin), app_plugins.LibCRPlugin(), app_plugins.LibCRIPCPlugin(), diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index 8009c0c..26eb66d 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -260,20 +260,12 @@ class Lib9PPlugin: lib9p_msgs: set[str] _CONFIG_9P_MAX_CONNS: int | None _CONFIG_9P_MAX_REQS: int | None - formatters: typing.Collection[BaseName] def __init__( self, arg_base_dir: str, arg_c_fnames: typing.Collection[str], - libmisc_plugin: LibMiscPlugin, ) -> None: - self.formatters = { - x.base() - for x in libmisc_plugin.objcalls["format"] - if str(x.base()).startswith("lib9p_") - } - # Find filenames ####################################################### def _is_config_h(fname: str) -> bool: @@ -347,7 +339,7 @@ class Lib9PPlugin: re_table_call = re.compile( r"\s*_lib9p_(?Pvalidate|unmarshal|marshal)\(.*(?P[RT])msg.*\);\s*" ) - re_print_call = re.compile(r".*lib9p_table_msg.*\.box_as_fmt_formatter\(.*") + re_print_call = re.compile(r".*lib9p_table_msg.*\.print\(.*") def indirect_callees( self, loc: str, line: str @@ -366,9 +358,7 @@ class Lib9PPlugin: ], True if self.re_print_call.fullmatch(line): # Functions for disabled protocol extensions will be missing. - return [ - QName(f"lib9p_msg_{msg}_format") for msg in self.lib9p_msgs - ], True + return [QName(f"fmt_print_{msg}") for msg in self.lib9p_msgs], True if "lib9p/srv.c:" in loc: if "srv->msglog(" in line: # Actual ROMs shouldn't set this, and so will be missing on rp2040 builds. diff --git a/build-aux/measurestack/test_app_plugins.py b/build-aux/measurestack/test_app_plugins.py index 808d55a..3e281b7 100644 --- a/build-aux/measurestack/test_app_plugins.py +++ b/build-aux/measurestack/test_app_plugins.py @@ -8,7 +8,7 @@ import typing from . import analyze, app_plugins, testutil, util -from .analyze import BaseName, QName +from .analyze import QName def test_fct() -> None: @@ -70,102 +70,3 @@ def test_fct() -> None: 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) - - -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 | - # | | | - # | stdio_buffered_printer | - # | | - # |_____________________________________________________| - # - 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", "fmt_state_printf"}), - ("fmt_state_putchar", {"stdio_buffered_printer", "_out_buffer"}), - ("stdio_buffered_printer", {}), - ("_out_buffer", {}), # wrong fct - ] - - # 1 2 3 4 5 6 7 8 9 10 11 <= call_depth - # - main() ; + - # - __wrap__vprintf() ; + - # - fmt_vfctprintf() ; + - # - _vfctprintf() ; + - # - conv_builtin() ; - # - fmt_state_putchar() ; - # - stdio_buffered_printer() ; - # - libfmt_conv_formatter() ; + - # - lib9p_msg_Rread_format() ; + - # - fmt_state_putchar() ; - # - stdio_buffered_printer() ; - # - fmt_state_printf() ; + - # - _vfctprintf() ; + - # - conv_builtin() ; + - # - fmt_state_putchar() ; + - # - stdio_buffered_printer() ; + - # - libfmt_conv_formatter() ; skip (formatter won't use %v) - max_call_depth = 11 - 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", - ] - - 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) -- cgit v1.2.3-2-g168b From 3faaad9fe1f11cfe5699c6720c897bfddc7cf49a Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Thu, 15 May 2025 00:18:27 -0600 Subject: Begone with the printf variants of the log functions --- build-aux/measurestack/app_main.py | 1 - build-aux/measurestack/app_plugins.py | 7 ------- build-aux/measurestack/test_app_plugins.py | 2 -- 3 files changed, 10 deletions(-) (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_main.py b/build-aux/measurestack/app_main.py index 69247ed..463447e 100644 --- a/build-aux/measurestack/app_main.py +++ b/build-aux/measurestack/app_main.py @@ -88,7 +88,6 @@ def main( def misc_filter(name: QName) -> tuple[int, bool]: if name in [ QName("__assert_msg_fail"), - QName("__lm_printf"), QName("fmt_vfctprintf"), QName("fmt_vsnprintf"), ]: diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index 26eb66d..fbe137c 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -389,13 +389,6 @@ class PicoFmtPlugin: BaseName("stdio_vprintf"): BaseName("stdio_buffered_printer"), } ) - case "host": - self.known_fct.update( - { - # libfmt - BaseName("__lm_printf"): BaseName("libfmt_libc_fct"), - } - ) self.wont_call_v = set([*self.known_fct.values(), *wont_call_v]) def is_intrhandler(self, name: QName) -> bool: diff --git a/build-aux/measurestack/test_app_plugins.py b/build-aux/measurestack/test_app_plugins.py index 3e281b7..aed0bb4 100644 --- a/build-aux/measurestack/test_app_plugins.py +++ b/build-aux/measurestack/test_app_plugins.py @@ -49,8 +49,6 @@ def test_fct() -> None: # if rp2040: # __wrap_vprintf => fct=stdio_buffered_printer # stdio_vprintf => fct=stdio_buffered_printer - # if host: - # __lm_printf => fct=libfmt_libc_fct app_plugins.PicoFmtPlugin("rp2040", []), ] -- cgit v1.2.3-2-g168b From e38a2d29292ad3fad64729ef958ff07e3bca02cf Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Thu, 15 May 2025 01:59:17 -0600 Subject: Fully banish printf from the firmware --- build-aux/measurestack/app_main.py | 3 - build-aux/measurestack/app_plugins.py | 97 +----------------------------- build-aux/measurestack/test_app_plugins.py | 70 --------------------- 3 files changed, 1 insertion(+), 169 deletions(-) delete mode 100644 build-aux/measurestack/test_app_plugins.py (limited to 'build-aux/measurestack') diff --git a/build-aux/measurestack/app_main.py b/build-aux/measurestack/app_main.py index 463447e..4fdfd5c 100644 --- a/build-aux/measurestack/app_main.py +++ b/build-aux/measurestack/app_main.py @@ -48,7 +48,6 @@ def main( plugins += [ app_plugins.CmdPlugin(), libmisc_plugin, - app_plugins.PicoFmtPlugin(arg_pico_platform, []), app_plugins.LibHWPlugin(arg_pico_platform, libmisc_plugin), app_plugins.LibCRPlugin(), app_plugins.LibCRIPCPlugin(), @@ -88,8 +87,6 @@ def main( def misc_filter(name: QName) -> tuple[int, bool]: if name in [ QName("__assert_msg_fail"), - QName("fmt_vfctprintf"), - QName("fmt_vsnprintf"), ]: return 1, False for prefix in ["fmt_print_", "_fmt_print_"]: diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index fbe137c..6fc81ec 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -19,7 +19,6 @@ __all__ = [ "LibCRIPCPlugin", "Lib9PPlugin", "LibMiscPlugin", - "PicoFmtPlugin", "PicoSDKPlugin", "TinyUSBDevicePlugin", "NewlibPlugin", @@ -369,97 +368,6 @@ class Lib9PPlugin: return {} -class PicoFmtPlugin: - known_fct: dict[BaseName, BaseName] - wont_call_v: typing.Collection[BaseName] - - def __init__( - self, arg_pico_platform: str, wont_call_v: typing.Collection[BaseName] - ) -> None: - self.known_fct = { - # pico_fmt - BaseName("fmt_vsnprintf"): BaseName("_out_buffer"), - } - match arg_pico_platform: - case "rp2040": - self.known_fct.update( - { - # pico_stdio - BaseName("__wrap_vprintf"): BaseName("stdio_buffered_printer"), - BaseName("stdio_vprintf"): BaseName("stdio_buffered_printer"), - } - ) - self.wont_call_v = set([*self.known_fct.values(), *wont_call_v]) - - 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 extra_nodes(self) -> typing.Collection[Node]: - return [] - - def indirect_callees( - self, loc: str, line: str - ) -> tuple[typing.Collection[QName], bool] | None: - if "/3rd-party/pico-fmt/" not in loc: - return None - if "/printf.c:" in loc: - m = util.re_call_other.fullmatch(line) - call: str | None = m.group("func") if m else None - if "->fct" in line: - return [x.as_qname() for x in self.known_fct.values()], False - if "specifier_table" in line: - return [ - # pico-fmt - QName("conv_sint"), - QName("conv_uint"), - # QName("conv_double"), - QName("conv_char"), - QName("conv_str"), - QName("conv_ptr"), - QName("conv_pct"), - # libfmt - QName("libfmt_conv_formatter"), - QName("libfmt_conv_quote"), - ], False - return None - - def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: - ret: dict[BaseName, analyze.SkipModel] = { - BaseName("fmt_state_putchar"): analyze.SkipModel( - self.known_fct.keys(), self._skipmodel_fmt_state_putchar - ), - BaseName("_vfctprintf"): analyze.SkipModel( - self.wont_call_v, self._skipmodel__vfctprintf - ), - } - return ret - - def _skipmodel_fmt_state_putchar( - self, chain: typing.Sequence[QName], node: Node, call: QName - ) -> bool: - if call.base() in self.known_fct.values(): - fct: BaseName | None = None - for pcall in reversed(chain): - if pcall.base() in self.known_fct: - fct = self.known_fct[pcall.base()] - return call.base() != fct - return True - return False - - def _skipmodel__vfctprintf( - self, chain: typing.Sequence[QName], node: Node, call: QName - ) -> bool: - if call.base() == BaseName("libfmt_conv_formatter"): - return any(c.base() in self.wont_call_v for c in chain) - return False - - class PicoSDKPlugin: get_init_array: typing.Callable[[], typing.Collection[QName]] app_init_array: typing.Collection[QName] | None @@ -877,10 +785,7 @@ class LibGCCPlugin: return False def init_array(self) -> typing.Collection[QName]: - return [ - QName("libfmt_install_formatter"), - QName("libfmt_install_quote"), - ] + return [] def extra_includes(self) -> typing.Collection[BaseName]: return [] diff --git a/build-aux/measurestack/test_app_plugins.py b/build-aux/measurestack/test_app_plugins.py deleted file mode 100644 index aed0bb4..0000000 --- a/build-aux/measurestack/test_app_plugins.py +++ /dev/null @@ -1,70 +0,0 @@ -# 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 QName - - -def test_fct() -> None: - # 1. | a + | b + | - # 2. | fmt_vsnprintf + | vprintf + | - # 3. | fmt_vfctprintf + | fmt_vfctprintf + | - # 4. | fmt_state_putchar + | fmt_state_putchar + | - # 5. | _out_buffer + | stdio_buffered_printer + | - max_call_depth = 5 - 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", - ] - graph: typing.Sequence[tuple[str, typing.Collection[str]]] = [ - # main.c - ("a", {"fmt_vsnprintf"}), # _out_buffer - ("b", {"vprintf"}), # stdio_buffered_printer - # wrappers - ("fmt_vsnprintf", {"fmt_vfctprintf"}), - ("__wrap_vprintf", {"fmt_vfctprintf"}), - # printf.c - ("fmt_vfctprintf", {"fmt_state_putchar"}), - ("fmt_state_putchar", {"_out_buffer", "stdio_buffered_printer"}), - # fcts - ("_out_buffer", {}), - ("stdio_buffered_printer", {}), - ] - 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 - 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) -- cgit v1.2.3-2-g168b