summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build-aux/measurestack/analyze.py123
-rw-r--r--build-aux/measurestack/app_plugins.py158
-rw-r--r--build-aux/measurestack/util.py12
3 files changed, 204 insertions, 89 deletions
diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py
index 8100813..a93874f 100644
--- a/build-aux/measurestack/analyze.py
+++ b/build-aux/measurestack/analyze.py
@@ -150,12 +150,38 @@ class AnalyzeResult(typing.NamedTuple):
included_funcs: set[QName]
+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` (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.
+
+ """
+
+ 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))):
+ if chain[i].base() in self.nchain:
+ _chain = chain[i - 1 :]
+ return self.fn(_chain, call), len(_chain)
+ return False, 0
+
+
class Application(typing.Protocol):
def extra_nodes(self) -> typing.Collection[Node]: ...
def indirect_callees(
self, elem: vcg.VCGElem
) -> tuple[typing.Collection[QName], bool]: ...
- def skip_call(self, chain: typing.Sequence[QName], funcname: QName) -> bool: ...
+ def skipmodels(self) -> dict[BaseName, SkipModel]: ...
# code #########################################################################
@@ -315,31 +341,20 @@ def analyze(
track_inclusion: bool = True
- def nstatic(
- orig_funcname: QName,
- chain: typing.Sequence[QName] = (),
- missing_ok: bool = False,
- ) -> int:
+ skipmodels = app.skipmodels()
+ for name, model in skipmodels.items():
+ if isinstance(model.nchain, int):
+ assert model.nchain > 1
+ else:
+ assert len(model.nchain) > 0
+
+ _nstatic_cache: dict[QName, int] = {}
+
+ def _nstatic(chain: list[QName], funcname: QName) -> tuple[int, int]:
nonlocal dbg
nonlocal track_inclusion
- funcname = graphdata.resolve_funcname(orig_funcname)
- if not funcname:
- if chain and app.skip_call(chain, orig_funcname):
- if dbg:
- print(f"//dbg: {'- '*len(chain)}{orig_funcname}\tskip missing")
- return 0
- if not missing_ok:
- missing.add(orig_funcname)
- if dbg:
- print(f"//dbg: {'- '*len(chain)}{orig_funcname}\tmissing")
- return 0
- if chain and app.skip_call(chain, funcname):
- if dbg:
- print(f"//dbg: {'- '*len(chain)}{orig_funcname}\tskip")
- return 0
-
- if len(chain) == cfg_max_call_depth:
- raise ValueError(f"max call depth exceeded: {[*chain, funcname]}")
+
+ assert funcname in graphdata.graph
node = graphdata.graph[funcname]
if dbg:
@@ -348,15 +363,57 @@ def analyze(
dynamic.add(funcname)
if track_inclusion:
included_funcs.add(funcname)
- return node.nstatic + max(
- [
- 0,
- *[
- nstatic(call, [*chain, funcname], missing_ok)
- for call, missing_ok in node.calls.items()
- ],
- ]
- )
+
+ max_call_nstatic = 0
+ max_call_nchain = 0
+
+ if node.calls:
+ skipmodel = skipmodels.get(funcname.base())
+ chain.append(funcname)
+ if len(chain) == cfg_max_call_depth:
+ raise ValueError(f"max call depth exceeded: {chain}")
+ for call_orig_qname, call_missing_ok in node.calls.items():
+ skip_nchain = 0
+ # 1. Resolve
+ call_qname = graphdata.resolve_funcname(call_orig_qname)
+ if not call_qname:
+ if skipmodel:
+ skip, _ = skipmodel(chain, call_orig_qname)
+ if skip:
+ if dbg:
+ print(
+ f"//dbg: {'- '*len(chain)}{call_orig_qname}\tskip missing"
+ )
+ continue
+ if not call_missing_ok:
+ missing.add(call_orig_qname)
+ if dbg:
+ print(f"//dbg: {'- '*len(chain)}{call_orig_qname}\tmissing")
+ continue
+
+ # 2. Skip
+ if skipmodel:
+ skip, skip_nchain = skipmodel(chain, call_qname)
+ max_call_nchain = max(max_call_nchain, skip_nchain)
+ if skip:
+ if dbg:
+ print(f"//dbg: {'- '*len(chain)}{call_qname}\tskip")
+ continue
+
+ # 3. Call
+ if skip_nchain == 0 and call_qname in _nstatic_cache:
+ max_call_nstatic = max(max_call_nstatic, _nstatic_cache[call_qname])
+ else:
+ call_nstatic, call_nchain = _nstatic(chain, call_qname)
+ max_call_nstatic = max(max_call_nstatic, call_nstatic)
+ max_call_nchain = max(max_call_nchain, call_nchain)
+ if skip_nchain == 0 and call_nchain == 0:
+ _nstatic_cache[call_qname] = call_nstatic
+ chain.pop()
+ return node.nstatic + max_call_nstatic, max(0, max_call_nchain - 1)
+
+ def nstatic(funcname: QName) -> int:
+ return _nstatic([], funcname)[0]
groups: dict[str, AnalyzeResultGroup] = {}
for grp_name, grp_filter in app_func_filters.items():
diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py
index 7eb7706..519e6b8 100644
--- a/build-aux/measurestack/app_plugins.py
+++ b/build-aux/measurestack/app_plugins.py
@@ -6,7 +6,7 @@
import re
import typing
-from . import util
+from . import analyze, util
from .analyze import BaseName, Node, QName
from .util import synthetic_node
@@ -51,8 +51,8 @@ class CmdPlugin:
return [QName("get_root")], False
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
re_comment = re.compile(r"/\*.*?\*/")
@@ -130,8 +130,8 @@ class LibObjPlugin:
], False
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
class LibHWPlugin:
@@ -199,8 +199,8 @@ class LibHWPlugin:
], False
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
class LibCRPlugin:
@@ -223,8 +223,8 @@ class LibCRPlugin:
) -> tuple[typing.Collection[QName], bool] | None:
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
class LibCRIPCPlugin:
@@ -252,8 +252,8 @@ class LibCRIPCPlugin:
], False
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
re_tmessage_handler = re.compile(
@@ -263,6 +263,9 @@ re_lib9p_msg_entry = re.compile(r"^\s*_MSG_(?:[A-Z]+)\((?P<typ>\S+)\),$")
re_lib9p_caller = re.compile(
r"^lib9p_(?P<grp>[TR])msg_(?P<meth>validate|unmarshal|marshal)$"
)
+re_lib9p_callee = re.compile(
+ r"^(?P<meth>validate|unmarshal|marshal)_(?P<msg>(?P<grp>[TR]).*)$"
+)
class Lib9PPlugin:
@@ -372,21 +375,48 @@ class Lib9PPlugin:
return [QName(f"{meth}_{msg}") for msg in self.lib9p_msgs], True
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- if "lib9p/srv.c:srv_util_pathfree" in str(call):
- assert isinstance(self.CONFIG_9P_SRV_MAX_DEPTH, int)
- if len(chain) >= self.CONFIG_9P_SRV_MAX_DEPTH and all(
- ("lib9p/srv.c:srv_util_pathfree" in str(c))
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ ret: dict[BaseName, analyze.SkipModel] = {
+ BaseName("_lib9p_validate"): analyze.SkipModel(
+ 2,
+ self._skipmodel__lib9p_validate_unmarshal_marshal,
+ ),
+ BaseName("_lib9p_unmarshal"): analyze.SkipModel(
+ 2,
+ self._skipmodel__lib9p_validate_unmarshal_marshal,
+ ),
+ BaseName("_lib9p_marshal"): analyze.SkipModel(
+ 2,
+ self._skipmodel__lib9p_validate_unmarshal_marshal,
+ ),
+ }
+ if isinstance(self.CONFIG_9P_SRV_MAX_DEPTH, int):
+ ret[BaseName("srv_util_pathfree")] = analyze.SkipModel(
+ self.CONFIG_9P_SRV_MAX_DEPTH,
+ self._skipmodel_srv_util_pathfree,
+ )
+ return ret
+
+ def _skipmodel__lib9p_validate_unmarshal_marshal(
+ self, chain: typing.Sequence[QName], call: QName
+ ) -> bool:
+ m_caller = re_lib9p_caller.fullmatch(str(chain[-2].base()))
+ assert m_caller
+
+ m_callee = re_lib9p_callee.fullmatch(str(call.base()))
+ if not m_callee:
+ return False
+ return m_caller.group("grp") != m_callee.group("grp")
+
+ def _skipmodel_srv_util_pathfree(
+ self, chain: typing.Sequence[QName], call: QName
+ ) -> bool:
+ assert isinstance(self.CONFIG_9P_SRV_MAX_DEPTH, int)
+ if call.base() == BaseName("srv_util_pathfree"):
+ return len(chain) >= self.CONFIG_9P_SRV_MAX_DEPTH and all(
+ c.base() == BaseName("srv_util_pathfree")
for c in chain[-self.CONFIG_9P_SRV_MAX_DEPTH :]
- ):
- return True
- wrapper = next((c for c in chain if re_lib9p_caller.match(str(c))), None)
- if wrapper:
- m = re_lib9p_caller.match(str(wrapper))
- assert m
- deny = m.group("meth") + "_" + ("R" if m.group("grp") == "T" else "T")
- if str(call.base()).startswith(deny):
- return True
+ )
return False
@@ -408,14 +438,20 @@ class LibMiscPlugin:
) -> tuple[typing.Collection[QName], bool] | None:
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- if (
- len(chain) > 1
- and chain[-1].base() == BaseName("__assert_msg_fail")
- and call.base() == BaseName("__lm_printf")
- and any(c.base() == BaseName("__assert_msg_fail") for c in chain[:-1])
- ):
- return True
+ 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], call: QName
+ ) -> bool:
+ if call.base() in [BaseName("__lm_printf")]:
+ return any(
+ c.base() == BaseName("__assert_msg_fail") for c in reversed(chain[:-1])
+ )
return False
@@ -462,23 +498,45 @@ class PicoFmtPlugin:
return [x.as_qname() for x in self.known_fct.values()], False
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ ret: dict[BaseName, analyze.SkipModel] = {
+ BaseName("_out_rev"): analyze.SkipModel(
+ self.known_out.keys(), self._skipmodel_outcaller
+ ),
+ BaseName("_etoa"): analyze.SkipModel(
+ self.known_out.keys(), self._skipmodel_outcaller
+ ),
+ BaseName("_vsnprintf"): analyze.SkipModel(
+ self.known_out.keys(), self._skipmodel_outcaller
+ ),
+ BaseName("_out_fct"): analyze.SkipModel(
+ self.known_fct.keys(), self._skipmodel__out_fct
+ ),
+ }
+ return ret
+
+ def _skipmodel_outcaller(self, chain: typing.Sequence[QName], call: QName) -> bool:
if call.base() in self.known_out.values():
out: BaseName | None = None
- for pcall in chain:
+ for pcall in reversed(chain):
if pcall.base() in self.known_out:
out = self.known_out[pcall.base()]
- if out == BaseName("_out_buffer") and call.base() == BaseName(
- "_out_null"
- ): # XXX: Gross hack
- out = BaseName("_out_null")
- return call.base() != out
+ if out == BaseName("_out_buffer") and call.base() == BaseName(
+ "_out_null"
+ ): # XXX: Gross hack
+ out = BaseName("_out_null")
+ return call.base() != out
+ return True
+ return False
+
+ def _skipmodel__out_fct(self, chain: typing.Sequence[QName], call: QName) -> bool:
if call.base() in self.known_fct.values():
fct: BaseName | None = None
- for pcall in chain:
+ for pcall in reversed(chain):
if pcall.base() in self.known_fct:
fct = self.known_fct[pcall.base()]
- return call.base() != fct
+ return call.base() != fct
+ return True
return False
@@ -588,8 +646,8 @@ class PicoSDKPlugin:
return self.app_preinit_array, False
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
def extra_nodes(self) -> typing.Collection[Node]:
ret = []
@@ -820,8 +878,8 @@ class TinyUSBDevicePlugin:
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
class NewlibPlugin:
@@ -887,8 +945,8 @@ class NewlibPlugin:
) -> tuple[typing.Collection[QName], bool] | None:
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
class LibGCCPlugin:
@@ -917,5 +975,5 @@ class LibGCCPlugin:
) -> tuple[typing.Collection[QName], bool] | None:
return None
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool:
- return False
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ return {}
diff --git a/build-aux/measurestack/util.py b/build-aux/measurestack/util.py
index 5367da9..47b2617 100644
--- a/build-aux/measurestack/util.py
+++ b/build-aux/measurestack/util.py
@@ -6,7 +6,7 @@
import re
import typing
-from . import vcg
+from . import analyze, vcg
from .analyze import BaseName, Node, QName
# pylint: disable=unused-variable
@@ -82,7 +82,7 @@ class Plugin(typing.Protocol):
def indirect_callees(
self, loc: str, line: str
) -> tuple[typing.Collection[QName], bool] | None: ...
- def skip_call(self, chain: typing.Sequence[QName], call: QName) -> bool: ...
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]: ...
class PluginApplication:
@@ -118,8 +118,8 @@ class PluginApplication:
placeholder += " at " + self._location_xform(elem.attrs.get("label", ""))
return [QName(placeholder)], False
- def skip_call(self, chain: typing.Sequence[QName], funcname: QName) -> bool:
+ def skipmodels(self) -> dict[BaseName, analyze.SkipModel]:
+ ret: dict[BaseName, analyze.SkipModel] = {}
for plugin in self._plugins:
- if plugin.skip_call(chain, funcname):
- return True
- return False
+ ret.update(plugin.skipmodels())
+ return ret