diff options
Diffstat (limited to 'build-aux')
-rw-r--r-- | build-aux/measurestack/analyze.py | 55 | ||||
-rw-r--r-- | build-aux/measurestack/app_plugins.py | 18 | ||||
-rw-r--r-- | build-aux/measurestack/test_app_plugins.py | 137 |
3 files changed, 170 insertions, 40 deletions
diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py index f454b7e..67c44ce 100644 --- a/build-aux/measurestack/analyze.py +++ b/build-aux/measurestack/analyze.py @@ -231,28 +231,39 @@ class AnalyzeResult(typing.NamedTuple): class SkipModel(typing.NamedTuple): """Running the skipmodel calls `.fn(chain, ...)` with the chain - consisting of the last `.nchain` items (if .nchain is an int), or - the chain starting with the *last* occurance of `.nchain` in - chain[:-1] (if .nchain is a collection). If the chain is not that - long or does not contain a member of the collection, then .fn is - not called and the call is *not* skipped. + consisting of the last few items of the input chain. + If `.nchain` is an int: + + - the chain is the last `.nchain` items or the input chain. If + the input chain is not that long, then `.fn` is not called and + the call is *not* skipped. + + If `.nchain` is a collection: + + - the chain starts with the *last* occurance of `.nchain` in the + input chain. If the input chain does not contain a member of + the collection, then .fn is called with an empty chain. """ nchain: int | typing.Collection[BaseName] - fn: typing.Callable[[typing.Sequence[QName], QName], bool] - - def __call__(self, chain: typing.Sequence[QName], call: QName) -> tuple[bool, int]: - if isinstance(self.nchain, int): - if len(chain) >= self.nchain: - _chain = chain[-self.nchain :] - return self.fn(_chain, call), len(_chain) - else: - for i in reversed(range(len(chain) - 1)): - if chain[i].base() in self.nchain: - _chain = chain[i:] - return self.fn(_chain, call), len(_chain) - return False, 0 + fn: typing.Callable[[typing.Sequence[QName], Node, QName], bool] + + def __call__( + self, chain: typing.Sequence[QName], node: Node, call: QName + ) -> tuple[bool, int]: + match self.nchain: + case int(): + if len(chain) >= self.nchain: + _chain = chain[-self.nchain :] + return self.fn(_chain, node, call), len(_chain) + 1 + return False, 0 + case _: + for i in reversed(range(len(chain))): + if chain[i].base() in self.nchain: + _chain = chain[i:] + return self.fn(_chain, node, call), len(_chain) + 1 + return self.fn([], node, call), 1 class Application(typing.Protocol): @@ -458,9 +469,7 @@ def analyze( skipmodels = app.skipmodels() for name, model in skipmodels.items(): - if isinstance(model.nchain, int): - assert model.nchain > 1 - else: + if not isinstance(model.nchain, int): assert len(model.nchain) > 0 _nstatic_cache: dict[QName, int] = {} @@ -495,7 +504,7 @@ def analyze( call_qname = graphdata.resolve_funcname(call_orig_qname) if not call_qname: if skipmodel: - skip, _ = skipmodel(chain, call_orig_qname) + skip, _ = skipmodel(chain[:-1], node, call_orig_qname) if skip: if dbg_nstatic: putdbg(f"{call_orig_qname}\tskip missing") @@ -508,7 +517,7 @@ def analyze( # 2. Skip if skipmodel: - skip, skip_nchain = skipmodel(chain, call_qname) + skip, skip_nchain = skipmodel(chain[:-1], node, call_qname) max_call_nchain = max(max_call_nchain, skip_nchain) if skip: if dbg_nstatic: diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index a7d4647..8eda36c 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -136,11 +136,11 @@ class LibMiscPlugin: } def _skipmodel___assert_msg_fail( - self, chain: typing.Sequence[QName], call: QName + 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[:-1]) + c.base() == BaseName("__assert_msg_fail") for c in reversed(chain) ) return False @@ -399,15 +399,15 @@ class Lib9PPlugin: def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: ret: dict[BaseName, analyze.SkipModel] = { BaseName("_lib9p_validate"): analyze.SkipModel( - 2, + 1, self._skipmodel__lib9p_validate_unmarshal_marshal, ), BaseName("_lib9p_unmarshal"): analyze.SkipModel( - 2, + 1, self._skipmodel__lib9p_validate_unmarshal_marshal, ), BaseName("_lib9p_marshal"): analyze.SkipModel( - 2, + 1, self._skipmodel__lib9p_validate_unmarshal_marshal, ), BaseName("_vfctprintf"): analyze.SkipModel( @@ -417,9 +417,9 @@ class Lib9PPlugin: return ret def _skipmodel__lib9p_validate_unmarshal_marshal( - self, chain: typing.Sequence[QName], call: QName + self, chain: typing.Sequence[QName], node: Node, call: QName ) -> bool: - m_caller = self.re_lib9p_caller.fullmatch(str(chain[-2].base())) + m_caller = self.re_lib9p_caller.fullmatch(str(chain[-1].base())) assert m_caller m_callee = self.re_lib9p_callee.fullmatch(str(call.base())) @@ -428,7 +428,7 @@ class Lib9PPlugin: return m_caller.group("grp") != m_callee.group("grp") def _skipmodel__vfctprintf( - self, chain: typing.Sequence[QName], call: QName + self, chain: typing.Sequence[QName], node: Node, call: QName ) -> bool: if call.base() == BaseName("libfmt_conv_formatter"): return any(c.base() in self.formatters for c in chain) @@ -510,7 +510,7 @@ class PicoFmtPlugin: return ret def _skipmodel_fmt_state_putchar( - self, chain: typing.Sequence[QName], call: QName + self, chain: typing.Sequence[QName], node: Node, call: QName ) -> bool: if call.base() in self.known_fct.values(): fct: BaseName | None = None diff --git a/build-aux/measurestack/test_app_plugins.py b/build-aux/measurestack/test_app_plugins.py index abdccca..8aa0a6c 100644 --- a/build-aux/measurestack/test_app_plugins.py +++ b/build-aux/measurestack/test_app_plugins.py @@ -104,15 +104,21 @@ def test_assert_msg_fail() -> None: 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" + 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 call in [QName("__lm_light_printf"), QName("__lm_abort")] - return orig_model.fn(chain, 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 @@ -136,3 +142,118 @@ def test_assert_msg_fail() -> None: 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], + ) |