@h2loop/safe-c-coding-plugin 0.1.0 → 0.3.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.
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
mode: subagent
|
|
3
3
|
displayName: Safe-C Reviewer
|
|
4
4
|
description: >-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
for
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
ALWAYS invoke this subagent once you have written, generated, or modified any C or C++
|
|
6
|
+
code (.c, .h, .cpp, .hpp), or whenever you want to review existing C/C++ code — do not
|
|
7
|
+
skip this for one-line changes or code that looks routine. Reviews the code against the
|
|
8
|
+
safe-c-coding (MISRA C:2012-aligned) guidelines. Given a file, diff, or code range, it
|
|
9
|
+
scans for undefined behaviour, dynamic allocation, unchecked returns, implicit
|
|
10
|
+
conversions, missing switch defaults, recursion, and other hazardous constructs, and
|
|
11
|
+
reports each as "severity — file:line — what's wrong -> safe fix". Use it to audit
|
|
12
|
+
hand-written or generated C/C++ before it lands, especially for embedded / safety-critical
|
|
13
|
+
targets.
|
|
11
14
|
permission:
|
|
12
15
|
# deny-all FIRST, then allow specifics: PermissionNext resolves with findLast
|
|
13
16
|
# (last matching rule wins), so "*": deny MUST precede the allows or it overrides them.
|
|
@@ -34,7 +37,16 @@ Procedure:
|
|
|
34
37
|
3. **Scan section by section** against the guidelines: undefined behaviour, dynamic memory
|
|
35
38
|
(`malloc`/`free`/VLAs), unchecked return values, implicit / narrowing conversions,
|
|
36
39
|
signed-unsigned mixing, uninitialised reads, missing `default` in `switch`, recursion,
|
|
37
|
-
unbraced bodies, `goto`/`longjmp`/`exit`/`abort`, multiple points of exit, etc.
|
|
40
|
+
unbraced bodies, `goto`/`longjmp`/`exit`/`abort`, multiple points of exit, etc. Two checks
|
|
41
|
+
need you to look outside the reviewed lines, so do them deliberately rather than by eye:
|
|
42
|
+
**(a) undeclared identifiers** — for each macro, struct/union member, type, constant, or
|
|
43
|
+
function the code references but does not define, `grep` the project's headers for its
|
|
44
|
+
declaration; a name with no declaration is a Critical finding, not a typo, and the fix is
|
|
45
|
+
the real name from the header. **(b) guards that cannot fail** — for each `if`/loop
|
|
46
|
+
condition, trace where the tested value comes from; if every operand is fixed at compile
|
|
47
|
+
time (macro, enum constant, `sizeof`, initialised `const`, object address) the branch is
|
|
48
|
+
always-true or always-false dead code, and the fix is to drop the guard or move the check
|
|
49
|
+
to `#if`/static assertion.
|
|
38
50
|
4. **Every finding gets a safe fix.** A finding without the corrective idiom is incomplete —
|
|
39
51
|
name the safe replacement (static buffer / fixed-size pool, checked status return, explicit
|
|
40
52
|
cast, added `default`, single point of exit) and, where useful, the substitute pattern from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h2loop/safe-c-coding-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Safe-C coding plugin for Hydron — MISRA C:2012-aligned defensive C/C++ guidelines as an always-on skill plus a safe-c-reviewer subagent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -21,10 +21,14 @@
|
|
|
21
21
|
],
|
|
22
22
|
"files": [
|
|
23
23
|
".hydron-plugin",
|
|
24
|
+
"plugins",
|
|
24
25
|
"skills",
|
|
25
26
|
"agents",
|
|
26
27
|
"README.md"
|
|
27
28
|
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@kilocode/plugin": "*"
|
|
31
|
+
},
|
|
28
32
|
"repository": {
|
|
29
33
|
"type": "git",
|
|
30
34
|
"url": "git+https://github.com/h2loop/safe-c-misra-skill-plugin.git"
|
package/plugins/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Plugin } from "@kilocode/plugin"
|
|
2
|
+
|
|
3
|
+
export const SafeCCodingPlugin: Plugin = async () => ({
|
|
4
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
5
|
+
output.system.push(
|
|
6
|
+
"Before writing, generating, modifying, or reviewing any C or C++ code " +
|
|
7
|
+
"(.c, .h, .cpp, .hpp), invoke the safe-c-coding skill. After writing or " +
|
|
8
|
+
"modifying C/C++ code, invoke the safe-c-reviewer subagent. Do this even " +
|
|
9
|
+
"for a one-line change or code that looks routine.",
|
|
10
|
+
)
|
|
11
|
+
},
|
|
12
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: safe-c-coding
|
|
3
|
-
description:
|
|
3
|
+
description: ALWAYS invoke this skill before writing, generating, modifying, or reviewing any C or C++ code (.c, .h, .cpp, .hpp) — no exception for one-line changes, snippets, or code that looks routine. Applies especially to embedded, automotive, medical, aerospace, or other safety-critical or resource-constrained targets where reliability, determinism, and freedom from undefined behaviour matter, but the mandatory-invocation rule applies to all C/C++ work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Safe C Coding
|
|
@@ -13,7 +13,23 @@ A set of defensive coding guidelines for writing C (and C-style C++) that is rel
|
|
|
13
13
|
|
|
14
14
|
> **Apply this as you write.** Whenever you write, generate, or modify C or C++ — even a one-line snippet or a quick example — follow these MISRA C:2012-aligned guidelines proactively, without waiting to be asked to review. Produce compliant code the first time; do not emit a hazardous construct now and plan to fix it later. If a guideline genuinely cannot be met, flag it and justify the deviation rather than silently violating it.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
## Non-negotiables
|
|
17
|
+
|
|
18
|
+
Short list, verified on every function you write or edit. Everything else lives in guidelines.md; these are the ones that get missed in practice.
|
|
19
|
+
|
|
20
|
+
1. **Every non-constant array index is guarded by a real `if` or loop condition.** When a function subscripts an array with an index it received as a parameter, read from a struct or global, or computed, the range check is control flow — `if (i < LIMIT) { ...access... }` — placed on the path to the access. An assertion macro, a comment, a documented caller contract, and the same shape appearing in a neighbouring function are none of them guards.
|
|
21
|
+
2. **Every pointer dereference is guarded the same way** — `if (p != NULL)`, not asserted.
|
|
22
|
+
3. **No dynamic memory, recursion, VLAs, `exit`/`abort`/`longjmp`, or unbounded string functions** — use the substitutes in guidelines.md.
|
|
23
|
+
4. **No implicit narrowing and no signed/unsigned mixing** — every conversion explicit.
|
|
24
|
+
5. **Braces on every body, a `default` on every `switch`, every return value used or `(void)`-discarded.**
|
|
25
|
+
6. **Every identifier you reference is one you have actually read in a declaration.** Before writing a macro, struct or union member, type, enum constant, global, or function name that this file does not itself define, find its declaration in the headers and context you were given, and copy the spelling from there. Names that feel like they *must* exist — a size or count macro accompanying a table, a member named after the quantity it holds, an obvious-looking helper — very often do not, and a plausible invented name is a compile error, not a style blemish. Note in particular that a length may be exposed as a variable rather than a macro, and that a member's name usually describes its slot, not its physical meaning. If you cannot find a declaration for something you need, say so rather than guessing at it.
|
|
26
|
+
7. **Every guard you add must be able to fail.** Before testing a value against zero, NULL, or a range limit, establish what the value actually is. If it is fixed at compile time — a macro expanding to a literal expression, an enum constant, a `sizeof`, a `const` object with a visible initialiser, the address of a declared object or array — then the test has one permanent answer, and an always-true or always-false controlling expression is a required-rule violation (dead code), not extra safety. Guard the things that genuinely vary at run time: parameters, values read from globals or hardware, results of computation. If the worry is that a *configured constant* could be set wrongly, check it at compile time with `#if` or a static-assertion idiom, which costs nothing at run time and cannot be dead code. This does **not** loosen items 1 and 2: an index or pointer that arrives as a parameter, or is read from a global, a struct, or a device, is precisely the varying kind, and still needs its real `if` guard. The two rules point the same way — put guards where a value can actually surprise you, and nowhere else.
|
|
27
|
+
|
|
28
|
+
Where one of these clashes with the surrounding house style, follow the guideline in the code you write and note the divergence in one line.
|
|
29
|
+
|
|
30
|
+
The full guideline catalogue is in **[guidelines.md](guidelines.md)**.
|
|
31
|
+
|
|
32
|
+
> **Mandatory: read guidelines.md in full before writing any code.** This overview is not a substitute for it — it omits the specific defensive patterns (e.g. the exact bounds-check idiom, the banned-facility substitute list) that make code actually compliant. Read the file with the read tool once per task, before your first edit, even for a one-line change or a task that looks routine. Do not rely on memory of MISRA rules or on this overview alone.
|
|
17
33
|
|
|
18
34
|
## Severity
|
|
19
35
|
|
|
@@ -23,9 +39,40 @@ The full guideline catalogue is in **[guidelines.md](guidelines.md)**. Consult i
|
|
|
23
39
|
|
|
24
40
|
## When writing or modifying C/C++
|
|
25
41
|
|
|
26
|
-
1.
|
|
27
|
-
2.
|
|
28
|
-
3.
|
|
42
|
+
1. Read guidelines.md in full — before drafting any code, not after.
|
|
43
|
+
2. Choose the safe pattern from the start: static allocation over `malloc`, braced bodies, explicit types, checked return values, a `default` in every `switch`, no recursion, guarded array/pointer access.
|
|
44
|
+
3. Run the **self-audit** below on the code you just wrote, before you save or present it. This step is not optional and not a formality — it is where most violations are actually caught.
|
|
45
|
+
4. If a hazardous construct is genuinely unavoidable, don't hide it — call it out and explain why, with a documented justification.
|
|
46
|
+
5. If the project has a MISRA compliance checker (e.g. Cppcheck, Parasoft C/C++test, Helix QAC) and you don't already know how to invoke it for this project, ask the developer for the exact command to run it — then run it against the code you wrote and fix what it reports.
|
|
47
|
+
|
|
48
|
+
### Existing code is not the specification
|
|
49
|
+
|
|
50
|
+
The code already in the file, the neighbouring functions, and the project's house style are **evidence of local convention, not evidence of compliance**. Hand-written and legacy code frequently contains constructs these guidelines forbid; that is usually why the guidelines are being applied in the first place. So:
|
|
51
|
+
|
|
52
|
+
- Match the surrounding code's *naming, formatting, types, and interfaces* — those keep the codebase coherent.
|
|
53
|
+
- Do **not** inherit its *hazardous constructs*. "The function next to mine does it this way" is not a justification, and a static-analysis tool will not accept it — each construct is judged on its own.
|
|
54
|
+
- You will often be told not to modify existing code. That constrains what you may change; it does not license writing new non-compliant code that imitates it. Write the compliant form in the code you own, and note the divergence in one line if it looks inconsistent.
|
|
55
|
+
|
|
56
|
+
### Self-audit (run on your own diff, before saving)
|
|
57
|
+
|
|
58
|
+
Do this as a **separate pass with fresh eyes**, not as a mental note while writing — the audit only catches anything if it actually re-reads the code. Two acceptable forms, in order of preference:
|
|
59
|
+
|
|
60
|
+
- Delegate the code you just wrote to the **safe-c-reviewer** subagent (bundled with this skill) and fix every Critical and Standard finding it reports.
|
|
61
|
+
- If no subagent is available, re-read the edited region with the read tool — reading it back is part of the step — and work through the checklist below line by line.
|
|
62
|
+
|
|
63
|
+
Either way the pass is required before you save the file or report the work as done. A summary that says the code is compliant without a re-read behind it is not an audit.
|
|
64
|
+
|
|
65
|
+
Walk the lines you wrote — not the whole file — and answer each question explicitly, by re-reading the code rather than from memory of what you intended.
|
|
66
|
+
|
|
67
|
+
1. **Every array subscript and pointer dereference**: list them. For each, name the enclosing `if` or loop condition that proves the index is in range and the pointer is non-NULL *on that path*. If the only thing between the access and undefined behaviour is an assertion macro, a comment, a caller's promise, or a convention copied from nearby code, then it is unguarded — add a real guard.
|
|
68
|
+
2. **Every name you did not define in this file**: list the macros, struct/union members, types, enum constants, globals, and functions your new code references. For each, name the header or context file whose declaration you actually read. Any name you produced from memory, from the shape of a sibling name, or because it seemed like it ought to exist is unverified — go and find its declaration, and correct the spelling to match. This is a five-second check that prevents code that cannot compile.
|
|
69
|
+
3. **Every guard condition you added**: for each, state the value it tests and where that value comes from. If the answer is a macro, an enum constant, a `sizeof`, a `const` with a visible initialiser, or an array/object address, the condition is decided at compile time — remove the guard (or move the check to `#if`/static assertion) rather than shipping a branch that can only go one way. Keep only guards whose subject can actually change at run time.
|
|
70
|
+
4. **Every conversion**: is each narrowing or signed/unsigned mix explicit and provably safe?
|
|
71
|
+
5. **Every call**: is each return value used, or explicitly discarded with `(void)`?
|
|
72
|
+
6. **Every branch construct**: braced bodies, a `default` in every `switch`, a terminating `else` where the chain must be exhaustive.
|
|
73
|
+
7. **Anything on the banned-facility list** (dynamic memory, recursion, VLAs, `exit`/`abort`/`longjmp`, unbounded string functions): replaced by its substitute, or deviated from with a written justification.
|
|
74
|
+
|
|
75
|
+
State the result of the audit in one or two lines before you finish, so the check is visible rather than assumed.
|
|
29
76
|
|
|
30
77
|
## Preferred patterns
|
|
31
78
|
|
|
@@ -106,6 +153,22 @@ status_t process(const config_t *cfg)
|
|
|
106
153
|
}
|
|
107
154
|
```
|
|
108
155
|
|
|
156
|
+
**Bounds-checked array access** — a real control-flow guard, not just an assertion.
|
|
157
|
+
|
|
158
|
+
```c
|
|
159
|
+
status_t set_slot_value(item_t *table, size_t table_len, size_t index, float_t value)
|
|
160
|
+
{
|
|
161
|
+
status_t st = ERR_RANGE;
|
|
162
|
+
if (index < table_len) { /* real guard: seen by both the compiler and static analysis */
|
|
163
|
+
table[index].value = value;
|
|
164
|
+
st = OK;
|
|
165
|
+
}
|
|
166
|
+
return st;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
An assertion is not a bounds check. Projects commonly define their own assert macro (`assert`, `ASSERT`, `<PREFIX>_ASSERT`, `CHECK`, …), and such a macro is routinely compiled out in production builds — where it expands to nothing, the access it "protected" is simply unguarded. Even where it does halt on failure, static analysis has no way to know that, so it will not accept the assertion as proof that the following index is in range. Assert to document an invariant if the project's style calls for it, but still put a real `if` on the path to the access.
|
|
171
|
+
|
|
109
172
|
See guidelines.md for the full substitute list (banned facility → safe replacement).
|
|
110
173
|
|
|
111
174
|
## When reviewing C/C++
|
|
@@ -28,6 +28,7 @@ Defensive C guidelines for safety-critical and embedded code, aligned with the i
|
|
|
28
28
|
- Never let the address of a local (automatic) object outlive that object — don't return `&local` or store it somewhere persistent.
|
|
29
29
|
- Do not declare flexible array members.
|
|
30
30
|
- Do not use variable-length arrays.
|
|
31
|
+
- **(Critical)** Before indexing an array — or dereferencing a pointer — with a value that isn't a compile-time constant, guard the access with a real `if` (or an equivalent loop condition). This applies to a parameter the caller supplied, a value read from a struct or global, and any computed index. An assertion is not a substitute: `assert`, `ASSERT`, `<PREFIX>_ASSERT` and other project assert macros are commonly compiled out in production builds, and static analysis will not accept one as proof that the following index is in range. Assert the invariant as well if the project's style calls for it, but the `if` must be there. Neither a comment nor "the caller guarantees it" nor the shape of a neighbouring function counts as a guard. See SKILL.md Preferred patterns for the idiom.
|
|
31
32
|
|
|
32
33
|
## Types and conversions
|
|
33
34
|
|
|
@@ -67,7 +68,7 @@ Defensive C guidelines for safety-critical and embedded code, aligned with the i
|
|
|
67
68
|
- Always use braces for the body of an `if`, `else`, loop, or other selection/iteration statement — even a single statement.
|
|
68
69
|
- Terminate every multi-branch `if … else if` chain with a final `else`. This applies only when at least one `else if` is present; a standalone `if` with no `else if` does not require an `else`.
|
|
69
70
|
- A controlling expression of an `if` or loop should be genuinely Boolean — not a bare integer or pointer.
|
|
70
|
-
-
|
|
71
|
+
- **(Critical)** A controlling expression must not be always true or always false. This is most often introduced by a well-meant defensive guard placed around a value that cannot in fact vary: a divisor tested against zero when the divisor is a macro, an enum constant, a `sizeof`, or a `const` with a visible initialiser; a pointer tested against `NULL` when it is the address of a declared object or array; a limit test between two constants. Static analysis evaluates the constants and reports the branch as dead code — a required-rule violation — regardless of the intent behind it. Before adding any such guard, determine where the tested value comes from: guard it only if it is a parameter, a global, a hardware or database read, or a computed result that can genuinely take the guarded-against value. To protect against a *configuration constant* being set wrongly, use a compile-time check (`#if` with `#error`, or a static-assertion idiom) instead of a runtime `if`.
|
|
71
72
|
- A loop counter must not be a floating-point value.
|
|
72
73
|
- Keep `for` loops well-formed: one clear loop counter, with predictable init/test/update, not altered unexpectedly in the body.
|
|
73
74
|
- Use `goto` sparingly; if used, jump only forward to a label later in the same function and within an enclosing block.
|
|
@@ -135,6 +136,7 @@ A prohibition alone tends to get violated when the functionality is genuinely ne
|
|
|
135
136
|
|
|
136
137
|
## General
|
|
137
138
|
|
|
139
|
+
- **(Critical)** Reference only identifiers that are actually declared in scope. Every macro, struct or union member, type name, enum constant, object, and function you use must have a declaration you have read — in this file or in a header or context file you were given. Do not infer a name from convention, from a sibling identifier's shape, or from what the surrounding domain suggests ought to exist: an invented but plausible name is an undeclared-identifier error, and analysis tools report it as a required-rule violation rather than a typo. Two cases account for most of these mistakes — assuming a table or buffer has an accompanying size/count macro when its length is actually exposed some other way (a variable, a `sizeof` expression, a separate accessor), and naming a struct member after the quantity it carries rather than after the member's declared name. When the declaration cannot be found, stop and say so; do not fill the gap with a guess.
|
|
138
140
|
- All code should trace to a documented requirement; don't add unrequested functionality.
|
|
139
141
|
- Every source file must compile cleanly with no errors and no reliance on undefined or critical unspecified behaviour.
|
|
140
142
|
- Don't depend on implementation-defined behaviour unless it is identified and documented.
|