@h2loop/autosar 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +11 -1
  2. package/agents/autosar-integrator.md +45 -2
  3. package/commands/autosar-trace.md +2 -2
  4. package/package.json +4 -1
  5. package/plugins/index.js +55 -23
  6. package/python/ecuc_engine/__init__.py +1 -1
  7. package/python/ecuc_engine/add_signal.py +351 -0
  8. package/python/ecuc_engine/arxml_edit.py +387 -0
  9. package/python/ecuc_engine/audit.py +352 -50
  10. package/python/ecuc_engine/cli.py +125 -5
  11. package/python/ecuc_engine/codegen/__init__.py +233 -0
  12. package/python/ecuc_engine/codegen/binding.py +305 -0
  13. package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -0
  14. package/python/ecuc_engine/codegen/cdiff.py +395 -0
  15. package/python/ecuc_engine/codegen/cwriter.py +150 -0
  16. package/python/ecuc_engine/codegen/emit.py +463 -0
  17. package/python/ecuc_engine/codegen/handles.py +176 -0
  18. package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
  19. package/python/ecuc_engine/codegen/model.py +264 -0
  20. package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
  21. package/python/ecuc_engine/codegen/modules/com.py +145 -0
  22. package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
  23. package/python/ecuc_engine/codegen/modules/dem.py +22 -0
  24. package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
  25. package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
  26. package/python/ecuc_engine/codegen/registry.py +208 -0
  27. package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
  28. package/python/ecuc_engine/context.py +481 -0
  29. package/python/ecuc_engine/csrc.py +189 -0
  30. package/python/ecuc_engine/defs.py +30 -0
  31. package/python/ecuc_engine/ecucjson.py +75 -22
  32. package/python/ecuc_engine/find.py +164 -0
  33. package/python/ecuc_engine/gen.py +105 -0
  34. package/python/ecuc_engine/history.py +146 -0
  35. package/python/ecuc_engine/model.py +4 -1
  36. package/python/ecuc_engine/probe.py +229 -0
  37. package/python/ecuc_engine/rte_api.py +935 -0
  38. package/python/ecuc_engine/rules/__init__.py +5 -3
  39. package/python/ecuc_engine/rules/mem.py +84 -0
  40. package/python/ecuc_engine/rules/nvm.py +131 -0
  41. package/python/ecuc_engine/rules/pdur.py +87 -0
  42. package/python/ecuc_engine/rules/system.py +162 -0
  43. package/python/ecuc_engine/specids.py +163 -0
  44. package/python/ecuc_engine/sysmodel.py +152 -0
  45. package/python/ecuc_engine/validate.py +15 -2
  46. package/python/ecuc_engine/values.py +26 -0
  47. package/python/ecuc_engine/verify_rte.py +445 -0
  48. package/python/pyproject.toml +1 -1
  49. package/skills/autosar/SKILL.md +78 -6
  50. package/skills/autosar/references/codegen.md +58 -0
  51. package/skills/autosar/references/ecuc-model.md +4 -1
  52. package/skills/autosar/references/project-layout.md +10 -1
  53. package/skills/autosar/references/runnable-code.md +50 -0
@@ -0,0 +1,445 @@
1
+ """Verify Rte_* calls in C against the SW-C's expected RTE API (rte_api), per runnable.
2
+
3
+ Each call (found with the audit's call scanner) is mapped to its enclosing function, then to every runnable whose
4
+ entry point (the RUNNABLE-ENTITY SYMBOL) reaches that function through the local call graph, and classified:
5
+ OK the runnable has the access point for the API
6
+ NOT-ALLOWED the API exists for the SW-C (port/element/IRV/... declared) but this runnable has no access point
7
+ INVENTED no such API for the SW-C (unknown port, element, runnable, wrong direction/semantics, ...)
8
+ For NOT-ALLOWED and INVENTED it proposes the exact ARXML change as arxml_edit ops (checked with a dry run).
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import re
14
+ import subprocess
15
+ from typing import Optional
16
+
17
+ from . import paths
18
+ from .csrc import CIndex
19
+ from .rte_api import (COLLECTION, PER_RUNNABLE, ApiEntry, ArIndex, SwcModel, build_all, find_swc, needed_role,
20
+ split_name)
21
+
22
+ PROVIDE_KINDS = {"Write", "Send", "Invalidate", "Feedback", "IWrite", "IWriteRef", "IInvalidate", "Switch", "SwitchAck",
23
+ "Trigger"}
24
+ REQUIRE_KINDS = {"Read", "Receive", "DRead", "IsUpdated", "IRead", "IReadRef", "IStatus", "Call", "Result", "Prm", "Mode"}
25
+ SR_KINDS = {"Write", "Send", "Invalidate", "Feedback", "IWrite", "IWriteRef", "IInvalidate", "Read", "Receive", "DRead",
26
+ "IsUpdated", "IRead", "IReadRef", "IStatus"}
27
+ KIND_ROLE = {"Write": "dataSendPoint", "Send": "dataSendPoint", "Read": "dataReceivePointByArgument",
28
+ "Receive": "dataReceivePointByArgument", "DRead": "dataReceivePointByValue", "IRead": "dataReadAccess",
29
+ "IReadRef": "dataReadAccess", "IWrite": "dataWriteAccess", "IWriteRef": "dataWriteAccess",
30
+ "Call": "serverCallPoint", "Mode": "modeAccessPoint", "Switch": "modeSwitchPoint", "Prm": "parameterAccess",
31
+ "IrvRead": "readLocalVariable", "IrvIRead": "readLocalVariable", "IrvIReadRef": "readLocalVariable",
32
+ "IrvWrite": "writtenLocalVariable", "IrvIWrite": "writtenLocalVariable",
33
+ "IrvIWriteRef": "writtenLocalVariable"}
34
+ PREFIX = {"dataSendPoint": "SEND", "dataReceivePointByArgument": "REC", "dataReceivePointByValue": "RBV",
35
+ "dataReadAccess": "IREAD", "dataWriteAccess": "IWRITE", "serverCallPoint": "SCP",
36
+ "modeAccessPoint": "MAP", "modeSwitchPoint": "MSP", "parameterAccess": "PA", "readLocalVariable": "IRVR",
37
+ "writtenLocalVariable": "IRVW"}
38
+ # Not SW-C APIs: RTE lifecycle / BSW-facing / callbacks (SWS_RTE 5.8, 5.9) — left to the integration code
39
+ LIFECYCLE = re.compile(r"^Rte_(Start|Stop|Init|StartTiming|PartitionTerminated|PartitionRestarting|RestartPartition|"
40
+ r"COMCbk|ComCbk|SetMetaDataItem|GetMetaDataItem|GetMetaDataLength|MainFunction|"
41
+ r"ComDataMainFunction|Ports|NPorts|Port)\w*$")
42
+
43
+
44
+ # ---------------------------------------------------------------- git helpers
45
+ def changed_lines(root: str, ref: str) -> tuple:
46
+ """{abs file: set(lines) | None (=whole file)} of C changes in the work tree since ``ref``; error text."""
47
+ try:
48
+ r = subprocess.run(["git", "-C", root, "diff", "-U0", "--no-color", "--no-ext-diff", ref, "--"],
49
+ capture_output=True, text=True, timeout=120)
50
+ except (OSError, subprocess.TimeoutExpired) as e:
51
+ return {}, f"git diff failed: {e}"
52
+ if r.returncode != 0:
53
+ return {}, f"git diff {ref} failed: {r.stderr.strip()[:300]}"
54
+ top = subprocess.run(["git", "-C", root, "rev-parse", "--show-toplevel"], capture_output=True, text=True).stdout.strip() or root
55
+ out: dict = {}
56
+ cur = None
57
+ for ln in r.stdout.splitlines():
58
+ if ln.startswith("+++ "):
59
+ p = ln[4:].strip()
60
+ cur = None if p == "/dev/null" else os.path.join(top, p[2:] if p.startswith("b/") else p)
61
+ if cur and paths.is_c(cur):
62
+ out.setdefault(cur, set())
63
+ else:
64
+ cur = None
65
+ elif ln.startswith("@@") and cur:
66
+ m = re.search(r"\+(\d+)(?:,(\d+))?", ln)
67
+ if m:
68
+ a, n = int(m.group(1)), int(m.group(2) or 1)
69
+ out[cur].update(range(a, a + n))
70
+ u = subprocess.run(["git", "-C", root, "ls-files", "--others", "--exclude-standard"], capture_output=True, text=True)
71
+ for p in u.stdout.splitlines():
72
+ if paths.is_c(p):
73
+ out[os.path.join(root, p)] = None
74
+ return out, None
75
+
76
+
77
+ # ---------------------------------------------------------------- suggestions
78
+ class Suggest:
79
+ def __init__(self, ix: ArIndex, models: list):
80
+ self.ix, self.models = ix, models
81
+
82
+ def _type_ref(self, element: Optional[str]) -> tuple:
83
+ """A type for a new data element: the type of a same-named element elsewhere, else the platform uint8."""
84
+ if element:
85
+ for n in self.ix.of_tag("VARIABLE-DATA-PROTOTYPE", "PARAMETER-DATA-PROTOTYPE"):
86
+ if n.short_name == element and n.child_text("TYPE-TREF"):
87
+ return n.child_text("TYPE-TREF"), False
88
+ for n in self.ix.of_tag("IMPLEMENTATION-DATA-TYPE"):
89
+ if n.short_name == "uint8":
90
+ return self.ix.path(n), True
91
+ idts = self.ix.of_tag("IMPLEMENTATION-DATA-TYPE")
92
+ return (self.ix.path(idts[0]) if idts else "/AUTOSAR_Platform/ImplementationDataTypes/uint8"), True
93
+
94
+ def _unique(self, parent: str, base: str) -> str:
95
+ name, i = base, 2
96
+ while self.ix.get(f"{parent}/{name}") is not None:
97
+ name, i = f"{base}_{i}", i + 1
98
+ return name
99
+
100
+ def access_op(self, m: SwcModel, runnable: str, role: str, port_path: Optional[str], target: str,
101
+ port_name: str = "", element: str = "") -> dict:
102
+ r = m.runnable(runnable)
103
+ coll = COLLECTION.get(role)
104
+ if role in ("readLocalVariable", "writtenLocalVariable"):
105
+ values = {"ACCESSED-VARIABLE": {"LOCAL-VARIABLE-REF": "->" + target}}
106
+ tag = "VARIABLE-ACCESS"
107
+ elif role == "serverCallPoint":
108
+ tag, values = "SYNCHRONOUS-SERVER-CALL-POINT", {"OPERATION-IREF": {"CONTEXT-R-PORT-REF": "->" + port_path,
109
+ "TARGET-REQUIRED-OPERATION-REF": "->" + target}}
110
+ elif role == "modeAccessPoint":
111
+ prov = any(p["path"] == port_path and p["direction"] == "P" for p in m.ports)
112
+ side = "P" if prov else "R"
113
+ tag, values = "MODE-ACCESS-POINT", {"MODE-GROUP-IREF": {f"{side}-MODE-GROUP-IN-ATOMIC-SWC-INSTANCE-REF": {
114
+ f"CONTEXT-{side}-PORT-REF": "->" + port_path, "TARGET-MODE-GROUP-REF": "->" + target}}}
115
+ elif role == "modeSwitchPoint":
116
+ tag, values = "MODE-SWITCH-POINT", {"MODE-GROUP-IREF": {"CONTEXT-P-PORT-REF": "->" + port_path,
117
+ "TARGET-MODE-GROUP-REF": "->" + target}}
118
+ elif role == "parameterAccess":
119
+ tag = "PARAMETER-ACCESS"
120
+ values = {"ACCESSED-PARAMETER": ({"AUTOSAR-PARAMETER-IREF": {"PORT-PROTOTYPE-REF": "->" + port_path,
121
+ "TARGET-DATA-PROTOTYPE-REF": "->" + target}}
122
+ if port_path else {"LOCAL-PARAMETER-REF": "->" + target})}
123
+ else:
124
+ tag, values = "VARIABLE-ACCESS", {"ACCESSED-VARIABLE": {"AUTOSAR-VARIABLE-IREF": {
125
+ "PORT-PROTOTYPE-REF": "->" + port_path, "TARGET-DATA-PROTOTYPE-REF": "->" + target}}}
126
+ op = {"op": "add", "parent_path": r.path, "element": tag, "collection": coll, "values": values}
127
+ if tag != "MODE-ACCESS-POINT": # ModeAccessPoint is not identifiable
128
+ base = "_".join(x for x in (PREFIX.get(role, "AP"), port_name, element or target.rsplit("/", 1)[-1]) if x)
129
+ op["short_name"] = self._unique(r.path, base)
130
+ return op
131
+
132
+ def for_entry(self, m: SwcModel, e: ApiEntry, runnable: str) -> tuple:
133
+ role = needed_role(e)
134
+ if e.api in ("Enter", "Exit"):
135
+ r = m.runnable(runnable)
136
+ return [{"op": "add", "parent_path": r.path, "element": "CAN-ENTER-EXCLUSIVE-AREA-REF",
137
+ "collection": "CAN-ENTER-EXCLUSIVE-AREA-REFS", "values": "->" + e.target}], None
138
+ if role not in COLLECTION:
139
+ return [], f"add a {role} for {e.target} to runnable {runnable}"
140
+ hint = None
141
+ if e.runnable and e.runnable != runnable: # Rte_IWrite_<other re>_...: per-runnable APIs name the caller
142
+ fixed = e.name.replace(f"_{e.runnable}_", f"_{runnable}_", 1)
143
+ hint = f"{e.name} belongs to runnable {e.runnable}; from {runnable} call {fixed} (after adding the access point)"
144
+ return [self.access_op(m, runnable, role, e.port_path, e.target, e.port or "", e.element or "")], hint
145
+
146
+ def interfaces_with(self, kind: str, element: str) -> list:
147
+ tag, coll = {"sr": ("SENDER-RECEIVER-INTERFACE", "DATA-ELEMENTS"), "cs": ("CLIENT-SERVER-INTERFACE", "OPERATIONS"),
148
+ "mode": ("MODE-SWITCH-INTERFACE", "MODE-GROUP"), "prm": ("PARAMETER-INTERFACE", "PARAMETERS")}[kind]
149
+ out = []
150
+ for itf in self.ix.of_tag(tag):
151
+ holder = itf.child(coll)
152
+ kids = (holder.children if holder is not None and holder.tag == coll and coll != "MODE-GROUP"
153
+ else [holder] if holder is not None else [])
154
+ if any(k.short_name == element for k in kids):
155
+ out.append(self.ix.path(itf))
156
+ return out
157
+
158
+ def users_of(self, itf: str) -> list:
159
+ return sorted({f"{m.name}.{p['name']}" for m in self.models for p in m.ports if p["interface"] == itf})
160
+
161
+ def for_invented(self, m: SwcModel, name: str, runnable: Optional[str]) -> tuple:
162
+ """-> (ops, hint). ops create the missing port / element / IRV and the access point."""
163
+ kind, rest = split_name(name)
164
+ if kind is None:
165
+ return [], "not an RTE API name; use one of the allowed APIs listed by autosar_build_context"
166
+ run = m.runnable(runnable) if runnable else None
167
+ if kind in PER_RUNNABLE:
168
+ rn = next((r.name for r in sorted(m.runnables, key=lambda r: -len(r.name)) if rest.startswith(r.name + "_")), None)
169
+ if rn is None:
170
+ return [], f"Rte_{kind} must name the calling runnable: Rte_{kind}_{run.name if run else '<re>'}_..."
171
+ if run is not None and rn != run.name:
172
+ fixed = f"Rte_{kind}_{run.name}_{rest[len(rn) + 1:]}"
173
+ return [], f"names runnable {rn} but is called from {run.name}; per-runnable APIs use the caller: {fixed}"
174
+ rest = rest[len(rn) + 1:]
175
+ if run is None:
176
+ return [], "call site is not reachable from a runnable of this SW-C; cannot place an access point"
177
+ if kind.startswith("Irv"):
178
+ if m.behavior is None:
179
+ return [], f"{m.name} has no SWC-INTERNAL-BEHAVIOR"
180
+ implicit = kind.startswith("IrvI")
181
+ coll = "IMPLICIT-INTER-RUNNABLE-VARIABLES" if implicit else "EXPLICIT-INTER-RUNNABLE-VARIABLES"
182
+ tref, assumed = self._type_ref(rest)
183
+ irv = f"{m.behavior}/{rest}"
184
+ ops = [{"op": "add", "parent_path": m.behavior, "element": "VARIABLE-DATA-PROTOTYPE", "collection": coll,
185
+ "short_name": rest, "values": {"TYPE-TREF": "->" + tref}},
186
+ self.access_op(m, run.name, KIND_ROLE[kind], None, irv, "", rest)]
187
+ return ops, ("type assumed: " + tref + "; adjust TYPE-TREF" if assumed else None)
188
+ if kind in ("Enter", "Exit"):
189
+ ea = f"{m.behavior}/{rest}"
190
+ return [{"op": "add", "parent_path": m.behavior, "element": "EXCLUSIVE-AREA", "collection": "EXCLUSIVE-AREAS",
191
+ "short_name": rest},
192
+ {"op": "add", "parent_path": run.path, "element": "CAN-ENTER-EXCLUSIVE-AREA-REF",
193
+ "collection": "CAN-ENTER-EXCLUSIVE-AREA-REFS", "values": "->" + ea}], None
194
+ if kind in ("Pim", "CData", "IrTrigger", "Trigger", "SwitchAck", "Feedback", "Invalidate", "IInvalidate",
195
+ "IsUpdated", "IStatus", "Result"):
196
+ return [], explain(m, name)
197
+ role = KIND_ROLE.get(kind)
198
+ ports = [p for p in m.ports if rest.startswith(p["name"] + "_")]
199
+ port = max(ports, key=lambda p: len(p["name"])) if ports else None
200
+ if port is not None:
201
+ el = rest[len(port["name"]) + 1:]
202
+ elem = next((e for e in port["elements"] if e["name"] == el), None)
203
+ want_dir = "P" if kind in PROVIDE_KINDS else "R"
204
+ if elem is not None: # element exists: direction or semantics do not fit
205
+ alt = {"Write": "Read", "Read": "Write", "Send": "Receive", "Receive": "Send", "IWrite": "IRead",
206
+ "IRead": "IWrite"}.get(kind)
207
+ if want_dir not in port["direction"]:
208
+ return [], (f"port {port['name']} is a {port['direction']}-port; Rte_{kind} needs a "
209
+ f"{want_dir}-port" + (f" (on this port use Rte_{alt}_{port['name']}_{el})" if alt else ""))
210
+ sem = elem.get("semantics")
211
+ if sem == "data" and kind in ("Send", "Receive"):
212
+ return [], f"{el} has data semantics: use Rte_{'Write' if kind == 'Send' else 'Read'}_{port['name']}_{el}"
213
+ if sem == "event" and kind in ("Write", "Read", "IWrite", "IRead", "DRead"):
214
+ return [], f"{el} is queued (event semantics): use Rte_{'Send' if 'Write' in kind else 'Receive'}_{port['name']}_{el}"
215
+ return [], explain(m, name)
216
+ ik = port["interfaceKind"]
217
+ want = "sr" if kind in SR_KINDS else "cs" if kind == "Call" else "mode" if kind in ("Mode", "Switch") else "prm"
218
+ have = {"SENDER-RECEIVER-INTERFACE": "sr", "NV-DATA-INTERFACE": "sr", "CLIENT-SERVER-INTERFACE": "cs",
219
+ "MODE-SWITCH-INTERFACE": "mode", "PARAMETER-INTERFACE": "prm"}.get(ik)
220
+ if want != have or want not in ("sr", "prm"):
221
+ return [], f"port {port['name']} ({ik}) has no element '{el}'; Rte_{kind} needs a {want} element"
222
+ ops = self.new_element(port["interface"], want, el, kind)
223
+ target = f"{port['interface']}/{el}"
224
+ ops.append(self.access_op(m, run.name, role, port["path"], target, port["name"], el))
225
+ users = self.users_of(port["interface"])
226
+ hint = (f"adds element {el} to interface {port['interface']}, which is also used by: "
227
+ f"{', '.join(u for u in users if not u.startswith(m.name + '.')) or 'no other port'}")
228
+ return ops, hint
229
+ # unknown port: Rte_<k>_<port>_<element>, split at the last underscore
230
+ if "_" not in rest:
231
+ return [], f"Rte_{kind}_<port>_<element> expected, got '{rest}'"
232
+ pname, el = rest.rsplit("_", 1)
233
+ want = "sr" if kind in SR_KINDS else "cs" if kind == "Call" else "mode" if kind in ("Mode", "Switch") else "prm"
234
+ if role is None:
235
+ return [], explain(m, name)
236
+ cands = self.interfaces_with(want, el)
237
+ ops = []
238
+ notes = []
239
+ if cands:
240
+ itf = sorted(cands, key=lambda i: (-len(self.users_of(i)), i))[0]
241
+ notes.append(f"reuses existing interface {itf} (has {el}; used by {', '.join(self.users_of(itf)) or 'no port'})")
242
+ elif want in ("sr", "prm"):
243
+ pkg = self.interface_package(m)
244
+ iname = self._unique(pkg, f"{pname}_If")
245
+ itf = f"{pkg}/{iname}"
246
+ tref, assumed = self._type_ref(el)
247
+ elem = self.element_values(want, el, tref, kind)
248
+ ops.append({"op": "add", "parent_path": pkg, "element": "SENDER-RECEIVER-INTERFACE" if want == "sr"
249
+ else "PARAMETER-INTERFACE", "short_name": iname,
250
+ "values": ({"IS-SERVICE": False, "DATA-ELEMENTS": {"VARIABLE-DATA-PROTOTYPE": elem}} if want == "sr"
251
+ else {"PARAMETERS": {"PARAMETER-DATA-PROTOTYPE": elem}})})
252
+ if assumed:
253
+ notes.append(f"new interface {itf}: element type assumed {tref}; adjust TYPE-TREF")
254
+ else:
255
+ return [], f"no {want} interface with element '{el}' exists; create the interface first"
256
+ ptag = "P-PORT-PROTOTYPE" if kind in PROVIDE_KINDS else "R-PORT-PROTOTYPE"
257
+ ops.append({"op": "add", "parent_path": m.path, "element": ptag, "collection": "PORTS", "short_name": pname,
258
+ "values": {("PROVIDED-INTERFACE-TREF" if ptag.startswith("P") else "REQUIRED-INTERFACE-TREF"): "->" + itf}})
259
+ ops.append(self.access_op(m, run.name, role, f"{m.path}/{pname}", f"{itf}/{el}", pname, el))
260
+ notes.append(f"split '{rest}' as port '{pname}' + element '{el}'; connect the new port in the composition "
261
+ "and map it to a signal if it is inter-ECU")
262
+ return ops, "; ".join(notes)
263
+
264
+ def element_values(self, want: str, el: str, tref: str, kind: str) -> dict:
265
+ v = {"SHORT-NAME": el, "TYPE-TREF": "->" + tref}
266
+ if kind in ("Send", "Receive"):
267
+ v["SW-DATA-DEF-PROPS"] = {"SW-DATA-DEF-PROPS-VARIANTS": {"SW-DATA-DEF-PROPS-CONDITIONAL": {
268
+ "SW-IMPL-POLICY": "QUEUED"}}}
269
+ return v
270
+
271
+ def new_element(self, itf: str, want: str, el: str, kind: str) -> list:
272
+ tref, _assumed = self._type_ref(el)
273
+ v = self.element_values(want, el, tref, kind)
274
+ sn = v.pop("SHORT-NAME")
275
+ return [{"op": "add", "parent_path": itf, "element": "VARIABLE-DATA-PROTOTYPE" if want == "sr"
276
+ else "PARAMETER-DATA-PROTOTYPE", "collection": "DATA-ELEMENTS" if want == "sr" else "PARAMETERS",
277
+ "short_name": sn, "values": v}]
278
+
279
+ def interface_package(self, m: SwcModel) -> str:
280
+ for p in m.ports:
281
+ if p.get("interface") and self.ix.get(p["interface"]) is not None:
282
+ return p["interface"].rsplit("/", 1)[0]
283
+ return m.path.rsplit("/", 1)[0]
284
+
285
+
286
+ def explain(m: SwcModel, name: str) -> str:
287
+ from .rte_api import explain_unknown
288
+ return explain_unknown(m, name)
289
+
290
+
291
+ # ---------------------------------------------------------------- verify
292
+ def _swc_by_include(models: list, includes: list) -> Optional[SwcModel]:
293
+ inc = {os.path.basename(i).lower() for i in includes}
294
+ hits = [m for m in models if f"rte_{m.name}.h".lower() in inc]
295
+ return hits[0] if len(hits) == 1 else None
296
+
297
+
298
+ def verify(root: str, files: Optional[list] = None, diff_against: Optional[str] = None, swc: Optional[str] = None,
299
+ runnable: Optional[str] = None, check_ops: bool = True, project=None) -> dict:
300
+ notes: list = []
301
+ ix = ArIndex(root)
302
+ models = build_all(root, ix)
303
+ if not models:
304
+ return {"error": "no SW-COMPONENT-TYPE found in the project ARXML; nothing to verify against"}
305
+ only = find_swc(models, swc) if swc else None
306
+ if swc and only is None:
307
+ return {"error": f"no SW-COMPONENT-TYPE {swc!r}; known: {', '.join(m.name for m in models)}"}
308
+ if only and runnable and only.runnable(runnable) is None:
309
+ return {"error": f"{only.name} has no runnable {runnable!r}; runnables: {', '.join(r.name for r in only.runnables)}"}
310
+ cx = CIndex(root)
311
+ lines_filter: dict = {}
312
+ if diff_against:
313
+ ch, err = changed_lines(root, diff_against)
314
+ if err:
315
+ return {"error": err}
316
+ scan = sorted(f for f in ch if f.lower().endswith(".c"))
317
+ lines_filter = ch
318
+ if not scan:
319
+ notes.append(f"no C changes since {diff_against}")
320
+ elif files:
321
+ scan = sorted(f for f in paths.expand(root, files, paths.is_c) if f.lower().endswith(".c"))
322
+ if not scan:
323
+ return {"error": f"paths {files} matched no .c file under {root}"}
324
+ else:
325
+ scan = [f for f in cx.cfiles if f not in cx.generated]
326
+ for f in scan: # files outside the default index (explicit generated files, new files)
327
+ if f not in cx.src:
328
+ from .csrc import functions
329
+ fs, raw = functions(f)
330
+ cx.src[f] = raw
331
+ cx.includes[f] = re.findall(r'^\s*#\s*include\s*[<"]([^>"]+)[>"]', raw, re.M)
332
+ for fn in fs:
333
+ cx.funcs.setdefault(fn.name, []).append(fn)
334
+
335
+ # function -> [(model, runnable)] by reachability from runnable entry points
336
+ reach: dict = {}
337
+ entry: dict = {}
338
+ for m in models:
339
+ for r in m.runnables:
340
+ fn = cx.get(r.symbol)
341
+ entry[(m.name, r.name)] = fn
342
+ if fn is None:
343
+ continue
344
+ for g in cx.reachable(fn):
345
+ reach.setdefault(id(g), []).append((m, r))
346
+ sugg = Suggest(ix, models)
347
+ findings, other = [], []
348
+ counts = {"OK": 0, "NOT-ALLOWED": 0, "INVENTED": 0}
349
+ for f in scan:
350
+ lf = lines_filter.get(f, None) if diff_against else None
351
+ for fn in sorted((g for lst in cx.funcs.values() for g in lst if g.file == f), key=lambda g: g.line):
352
+ owners = reach.get(id(fn), [])
353
+ for name, line, args in fn.rte:
354
+ if diff_against and lf is not None and line not in lf:
355
+ continue
356
+ cands = owners
357
+ if only is not None:
358
+ cands = [(m, r) for m, r in owners if m is only] or ([(only, None)] if not owners else [])
359
+ if not cands:
360
+ continue # reachable only from other SW-Cs
361
+ if runnable:
362
+ cands = [(m, r) for m, r in cands if r is not None and runnable in (r.name, r.symbol)]
363
+ if not cands:
364
+ continue
365
+ if not cands:
366
+ m = _swc_by_include(models, cx.includes.get(f, [])) or next(
367
+ (x for x in models if name in x.apis), None)
368
+ if m is None:
369
+ if LIFECYCLE.match(name):
370
+ other.append({"call": name, "at": f"{cx.rel(f)}:{line}", "function": fn.name,
371
+ "why": "RTE lifecycle/BSW API, not a SW-C port API"})
372
+ continue
373
+ if len(models) != 1:
374
+ findings.append(_finding(cx, f, line, fn, name, args, None, None, "INVENTED",
375
+ "no SW-C in the project declares this API, and the calling "
376
+ "function belongs to no SW-C (no runnable reaches it, no "
377
+ "Rte_<Swc>.h include)", None))
378
+ counts["INVENTED"] += 1
379
+ continue
380
+ m = models[0]
381
+ cands = [(m, None)]
382
+ for m, r in cands:
383
+ status, e, why = m.classify(name, r.name if r else None)
384
+ if r is None:
385
+ why += f"; {fn.name} is not reached from any runnable of {m.name}, checked at SW-C level"
386
+ d = _finding(cx, f, line, fn, name, args, m, r, status, why, e)
387
+ if status != "OK" and r is not None:
388
+ ops, hint = (sugg.for_entry(m, e, r.name) if e is not None else sugg.for_invented(m, name, r.name))
389
+ if ops:
390
+ d["arxml_edit"] = ops
391
+ if hint:
392
+ d["hint"] = hint
393
+ elif status == "INVENTED":
394
+ _ops, hint = sugg.for_invented(m, name, None)
395
+ if hint:
396
+ d["hint"] = hint
397
+ counts[status] += 1
398
+ findings.append(d)
399
+ if check_ops:
400
+ _check_ops(root, findings, project)
401
+ unmapped = [{"swc": k[0], "runnable": k[1], "symbol": next(r.symbol for m in models if m.name == k[0]
402
+ for r in m.runnables if r.name == k[1])}
403
+ for k, v in entry.items() if v is None and (only is None or k[0] == only.name)]
404
+ res = {"scanned": {"files": [cx.rel(f) for f in scan], "functions": sum(len(v) for v in cx.funcs.values()),
405
+ "calls": sum(counts.values())},
406
+ "counts": counts, "findings": findings,
407
+ "swcs": [m.name for m in ([only] if only else models)]}
408
+ if other:
409
+ res["notChecked"] = other
410
+ if unmapped:
411
+ res["runnablesWithoutCode"] = unmapped
412
+ if notes:
413
+ res["notes"] = notes
414
+ return res
415
+
416
+
417
+ def _finding(cx, f, line, fn, name, args, m, r, status, why, e) -> dict:
418
+ d = {"status": status, "call": f"{name}({', '.join(args)})", "api": name, "at": f"{cx.rel(f)}:{line}",
419
+ "function": fn.name, "swc": m.name if m else None, "runnable": r.name if r else None, "why": why}
420
+ if e is not None:
421
+ d["expected"] = e.signature
422
+ d["cite"] = e.cite
423
+ return d
424
+
425
+
426
+ def _check_ops(root: str, findings: list, project=None):
427
+ """Dry-run each finding's ops with arxml_edit so only ready-to-run suggestions are labelled as such."""
428
+ todo = [d for d in findings if d.get("arxml_edit")]
429
+ if not todo:
430
+ return
431
+ try:
432
+ from .arxml_edit import arxml_edit
433
+ from .model import Project
434
+ p = project or Project(root, load_input=True)
435
+ except Exception as ex: # noqa: BLE001
436
+ for d in todo:
437
+ d["opsChecked"] = f"not checked: {ex}"
438
+ return
439
+ seen: dict = {}
440
+ for d in todo[:50]:
441
+ key = repr(d["arxml_edit"])
442
+ if key not in seen:
443
+ r = arxml_edit(p, d["arxml_edit"], dry_run=True)
444
+ seen[key] = True if r.get("ok") else (r.get("error") or "") + (" " + "; ".join(r.get("problems", [])) if r.get("problems") else "")
445
+ d["opsChecked"] = seen[key]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ecuc-engine"
3
- version = "1.0.1"
3
+ version = "1.4.0-pre"
4
4
  description = "Load, read, edit in place, validate and diff AUTOSAR ECUC ARXML (Hydron AUTOSAR pack)"
5
5
  requires-python = ">=3.10"
6
6
  # The core is stdlib-only; autosar-data adds a strict schema check of every edited file.
@@ -23,18 +23,45 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
23
23
  the param defs and patches only the changed bytes. Reading raw ARXML for context is fine; prefer `ecuc_read`.
24
24
  2. **Validate after every edit**: `ecuc_validate` (scoped with `files`/`module` if large). Fix
25
25
  every error you introduced before moving on; report pre-existing ones separately.
26
- 3. **Cite** the AUTOSAR path, the DEFINITION-REF and file:line for every element you discuss or change.
26
+ 3. **Cite** the AUTOSAR path, the DEFINITION-REF and file:line for every element you discuss or change —
27
+ copied from tool output (see *Citations and numbers*), never estimated.
27
28
  4. **Generated code is an output.** Never edit `Output/` (or other generated files) to fix a
28
29
  problem — change the configuration and regenerate.
29
30
  5. Values come from the param defs (ranges, literals, multiplicity) and the SWS; when the defs do
30
- not settle a question (cross-module consistency, handle IDs, timing), say so and cite the SWS
31
- requirement rather than guessing.
31
+ not settle a question (cross-module consistency, handle IDs, timing), say so plainly. Quote the SWS
32
+ requirement only if you have its text from a tool result this session; otherwise describe the rule
33
+ without an ID — never fill the gap from memory.
34
+
35
+ ## Citations and numbers (always)
36
+ - **Requirement IDs** (SWS_/SRS_/TPS_/ECUC_/RS_…): cite one only when it appears verbatim in a tool result
37
+ (e.g. the `spec:` line of `autosar_audit`, an H2L rule message, `autosar_spec_id_check`) or in spec text you
38
+ actually read this session. Before citing any other ID, run `autosar_spec_id_check ids=[…]`; cite it only if it
39
+ is DEFINED, and describe it with the returned text. If the check is unavailable or says NOT-FOUND /
40
+ MENTIONED-ONLY, describe the rule without an ID. An invented or misremembered ID is worse than none.
41
+ - **No requirement IDs for plain value questions** ("what is X", "which file sets Y", "trace Z"): answer with the
42
+ value, its AUTOSAR path and file:line. Add an ID only when the user asks why/what the standard requires.
43
+ - **file:line** must come from a tool result or a file you read: for a value use `"@at:<Param>"` (the VALUE
44
+ element) from `ecuc_read`/`ecuc_find`, or the `@ file:line` of a `VALUES` line from `autosar_trace` /
45
+ `autosar_config_lookup`; `@at` / `at` is the container. A graph `file:line` of an element not in the local
46
+ checkout is where the element starts: read the file to find the value line. Never estimate or offset a line.
47
+ - **Numbers and units**: never convert bases or units in your head. Copy `"@hex:<Param>"` / `"@dec:<Param>"`
48
+ (and the `dec …, hex …` of VALUES lines, CAN IDs, audit `…Hex` evidence). For any other conversion or
49
+ arithmetic (bytes ↔ bits, ms ↔ s, address + offset), compute it with a tool or say it is not computed.
50
+ - **Parameter semantics**: explain what a parameter means only from its param def (`ecuc_read schema=true`,
51
+ the def `DESC`) or spec text you read; if neither covers it, say the meaning is not in the loaded defs/specs
52
+ rather than inventing one.
53
+ - **Simple look-ups get short, exact answers**: the value (with hex if integer), the AUTOSAR path, the
54
+ DEFINITION-REF and file:line — no spec background, no padding.
32
55
 
33
56
  ## Tool map
34
57
  | Need | Tool |
35
58
  |---|---|
36
59
  | What modules/files/defs exist | `ecuc_read` (no path) |
37
60
  | Values of a module/container | `ecuc_read path=… depth=…` |
61
+ | All containers matching a condition / referencing X / referenced by Y | `ecuc_find` |
62
+ | Cross-check one module against the extract, C types and memory | `ecuc_crosscheck module=…` |
63
+ | Edit System extract / SWC / interface XML | `arxml_edit` (DEST inferred, schema-ordered, refs checked) |
64
+ | Add a signal end to end (ECUC + extract + DBC) | `autosar_add_signal` |
38
65
  | What may be set/added here (types, ranges, literals, multiplicity) | `ecuc_read path=… schema=true`, or a def path `/AUTOSAR/EcucDefs/<Mod>/…` |
39
66
  | Change config | `ecuc_edit ops=[…]` (`dry_run` first for large edits) |
40
67
  | Check config | `ecuc_validate` |
@@ -42,14 +69,39 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
42
69
  | Which C symbols does this config produce, who calls them / where does this symbol come from | `autosar_trace query=…` |
43
70
  | Refs in/out, children of an element in the ingested repo | `autosar_config_lookup path=…` |
44
71
  | Review C code that calls Com/NvM/Dem/PduR/RTE APIs | `autosar_audit` (first), then confirm with `autosar_trace` / `ecuc_read` |
45
- | Regenerate code | `autosar_generate` (configured command, asks permission) |
72
+ | Context for implementing / changing a runnable (allowed RTE API, code, callees, config, similar code/changes) | `autosar_build_context swc=… runnable=… task=…` |
73
+ | Check that C only calls the RTE APIs its runnable may call (OK / NOT-ALLOWED / INVENTED + ARXML fix ops) | `autosar_verify_rte` (`diff_against=<ref>` for a change) |
74
+ | Generate BSW config code ourselves (NvM/Fee/MemIf/Dem/PduR/CanIf/Port/Dio) | `autosar_codegen`, then `autosar_codegen_diff reference_dir=Output` (see `references/codegen.md`) |
75
+ | Regenerate code with the vendor tool | `autosar_generate` (configured command, asks permission) |
46
76
  | Compile/link | `autosar_build` |
77
+ | Is this requirement ID real, and what does it say? | `autosar_spec_id_check ids=[…]` (needs `specDocs` / `$AUTOSAR_SPEC_DOCS`) |
47
78
  | Spec questions (SWS/TPS requirement text) | the project's ingested docs search tools |
48
79
 
80
+ ## Workflow (validation and fix tasks)
81
+ 1. `ecuc_validate` unfiltered (all rules). It is **necessary but not sufficient**.
82
+ 2. Make a todo checklist with one item per module present (Com, PduR, CanIf, EcuC, NvM/Fee/Ea, Dem, Os, Rte, …)
83
+ and run `ecuc_crosscheck` for each; read its NOT CHECKED notes and close those gaps by hand.
84
+ 3. `autosar_audit` for how the C code uses the configuration (handles, directions, widths, RAM blocks).
85
+ 4. Fix only with `ecuc_edit` / `arxml_edit` / `autosar_add_signal`; then `autosar_generate`, `autosar_build`, and
86
+ re-run `ecuc_validate` (+ `checkGenerated: true` to prove `Output/` is not stale).
87
+ 5. **Never conclude "no faults" from a single validate call**, and never finish with a failing validation: after
88
+ ANY edit run `ecuc_validate`; if it fails, fix it before answering.
89
+
49
90
  ## The loop
50
91
  `ecuc_read (schema) → ecuc_edit → ecuc_validate → autosar_generate → arxml_diff / inspect Output → autosar_build → autosar_trace (impact on Source/)`.
51
92
  Details and failure handling: `references/workflow.md`.
52
93
 
94
+ ## Implement or change a runnable (SW-C application code)
95
+ 1. `autosar_build_context swc=<Swc> runnable=<Runnable> task="<the task verbatim>"` FIRST. Section 3 lists the RTE APIs
96
+ the runnable is ALLOWED to call, with the exact declarations from `Rte_<Swc>.h` (SWS_RTE naming and signatures);
97
+ sections 4–6 hold its code, callees and the Com/NvM config behind its ports; 7–8 are fuzzy hints only.
98
+ 2. Write the code using **only** the allowed APIs and their exact signatures (value vs pointer per SWS_Rte_01017/01019).
99
+ Never invent an `Rte_*` name; never call an API that belongs to another runnable.
100
+ 3. `autosar_verify_rte runnable=<Runnable>` (or `diff_against=<ref>` for the whole change).
101
+ 4. On INVENTED / NOT-ALLOWED: rewrite with an allowed API; or, only when the behaviour truly needs new data, apply the
102
+ returned `arxml_edit` ops (access point / port / data element), run `autosar_generate`, and verify again.
103
+ 5. `autosar_build`. Never finish with a failing verify or build. Details: `references/runnable-code.md`.
104
+
53
105
  ## Integration review recipe (C code against the configuration)
54
106
  1. `autosar_audit` (scope with `paths` if asked about specific files). It resolves every BSW call's handle
55
107
  through the ECUC and reports raw numeric handles, unknown handles, direction mismatches, C-width vs
@@ -58,9 +110,17 @@ Details and failure handling: `references/workflow.md`.
58
110
  `autosar_trace <symbol>` for the owning I-PDU, generated symbol and other callers.
59
111
  3. Review what the audit lists as *unchecked* by hand (pointer arguments, expressions as handles), and
60
112
  look for logic issues it cannot see (wrong signal chosen although types match, missing error handling).
61
- 4. Report: file:line, the call, the configured facts (AUTOSAR paths + values), the spec ID, the fix.
113
+ 4. Report: file:line, the call, the configured facts (AUTOSAR paths + values with their `…At` lines), the spec
114
+ ID exactly as the audit's `spec:` line gives it (never add others), the fix.
62
115
 
63
- Facts that decide common review calls:
116
+ Facts that decide common review calls (the IDs below are checked against R25-11; still cite one only when a
117
+ tool result this session shows it, per *Citations and numbers*):
118
+ - A Com signal's layout must match the System extract it came from (ISignalToIPduMapping startPosition /
119
+ packingByteOrder, ISignal length) and an EcuC Pdu's PduLength the system I-PDU length (H2L-SYS-001/002).
120
+ - A PduR routing path forwards one I-PDU: source and destination must stand for the same system I-PDU (or the same
121
+ I-SIGNAL content) — a gateway destination carrying a different I-PDU is wrong (H2L-PDUR-001).
122
+ - NvMNvBlockLength is the NV *data* length and must equal sizeof(the RAM block object); the Fee/Ea block must hold it
123
+ (H2L-NVM-001, sizeof measured by compiling a probe with the project's compiler and flags).
64
124
  - **Direction lives on the I-PDU**, not the signal: a ComSignal is TX or RX through the `ComIPduDirection`
65
125
  of the ComIPdu that references it (`ComIPduSignalRef`). Sending an RX signal (or receiving a TX one)
66
126
  fails at runtime (ECUC_Com_00493, SWS_Com_00197/00198).
@@ -77,8 +137,20 @@ Facts that decide common review calls:
77
137
  - `references/symbolic-names.md` — the symbolic-name rule (TPS_ECUC_02108) that links config to generated `#define`s, handle IDs.
78
138
  - `references/config-classes.md` — pre-compile / link-time / post-build config classes, variants, post-build loadable/selectable.
79
139
  - `references/project-layout.md` — Config / Input / Output / Source / Build / SIP and `.hydron/autosar.json`.
140
+ - `references/codegen.md` — deterministic code generation, handle-ID write-back, bindings, reading the semantic diff, MCAL hook.
141
+ - `references/runnable-code.md` — RTE API tiers (allowed / generated / derivable), naming and signature rules, verify statuses and fixes, context sections and budget.
80
142
  - `references/workflow.md` — the edit → validate → generate → build → trace loop, diagnostics rules, and what to do when a step fails.
81
143
 
144
+ ## Impact questions
145
+ Include, besides the trace to code: the **storage capacity** check (H2L-MEM-001, NvM/Fee/Ea vs MemAcc/Mem area) and
146
+ **NvM stored-data compatibility** — a changed RAM block size or layout makes existing NV data unreadable unless the
147
+ block is migrated (NvMResistantToChangedSw, NvMStaticBlockIDCheck, a new block ID or a data-version field).
148
+
149
+ ## MCAL questions
150
+ Public AUTOSAR documents define the MCAL interfaces and parameter definitions; vendor-specific configuration details
151
+ (register mapping, clock trees, pin multiplexing) live in the silicon vendor's MCAL user manuals, often under NDA. When
152
+ the ingested docs do not cover a detail, say so and point to the vendor MCAL manual instead of guessing.
153
+
82
154
  ## Scoping and empty results
83
155
  - Path arguments (`ecuc_validate files`, `autosar_audit paths`, `arxml_diff a/b`, `.hydron/autosar.json`
84
156
  globs) take files, directories (recursive) or globs (`**/Com*.arxml`), relative to the project root or