summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-05-02 01:38:12 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-05-06 11:50:46 -0600
commit6c755aadeb3ffff941667c70a93ef0e7cdd41a98 (patch)
tree04f19cbd7107f1a6d8e2267671e1923d4566243f
parent7259e7ffbebffa74c1c07257076bcb86fdf08689 (diff)
measurestack: Fix+test printf measurement
-rw-r--r--build-aux/measurestack/analyze.py55
-rw-r--r--build-aux/measurestack/app_plugins.py18
-rw-r--r--build-aux/measurestack/test_app_plugins.py137
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],
+ )