@pilllesss/yorn 1.0.182 → 1.0.183
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.
- package/README.md +1 -1
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/skills/code-review/LICENSE +21 -0
- package/dist/skills/code-review/SKILL.md +233 -0
- package/dist/skills/code-review/assets/pr-review-template.md +137 -0
- package/dist/skills/code-review/assets/review-checklist.md +123 -0
- package/dist/skills/code-review/reference/angular.md +768 -0
- package/dist/skills/code-review/reference/architecture-review-guide.md +472 -0
- package/dist/skills/code-review/reference/c.md +890 -0
- package/dist/skills/code-review/reference/code-quality-universal.md +488 -0
- package/dist/skills/code-review/reference/code-review-best-practices.md +136 -0
- package/dist/skills/code-review/reference/common-bugs-checklist.md +302 -0
- package/dist/skills/code-review/reference/cpp.md +893 -0
- package/dist/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
- package/dist/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
- package/dist/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
- package/dist/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
- package/dist/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
- package/dist/skills/code-review/reference/csharp.md +525 -0
- package/dist/skills/code-review/reference/css-less-sass.md +661 -0
- package/dist/skills/code-review/reference/dart.md +670 -0
- package/dist/skills/code-review/reference/django.md +985 -0
- package/dist/skills/code-review/reference/fastapi.md +580 -0
- package/dist/skills/code-review/reference/go.md +993 -0
- package/dist/skills/code-review/reference/java.md +409 -0
- package/dist/skills/code-review/reference/java8.md +586 -0
- package/dist/skills/code-review/reference/kotlin.md +1018 -0
- package/dist/skills/code-review/reference/nestjs.md +593 -0
- package/dist/skills/code-review/reference/performance-review-guide.md +816 -0
- package/dist/skills/code-review/reference/php.md +684 -0
- package/dist/skills/code-review/reference/python.md +1073 -0
- package/dist/skills/code-review/reference/qt.md +757 -0
- package/dist/skills/code-review/reference/react.md +871 -0
- package/dist/skills/code-review/reference/ruby.md +964 -0
- package/dist/skills/code-review/reference/rust.md +846 -0
- package/dist/skills/code-review/reference/security-review-guide.md +494 -0
- package/dist/skills/code-review/reference/svelte.md +1064 -0
- package/dist/skills/code-review/reference/swift.md +936 -0
- package/dist/skills/code-review/reference/typescript.md +1016 -0
- package/dist/skills/code-review/reference/vue.md +924 -0
- package/dist/skills/code-review/reference/zig.md +440 -0
- package/dist/skills/code-review/scripts/pr-analyzer.py +435 -0
- package/dist/skills/code-review/scripts/test_pr_analyzer.py +380 -0
- package/dist/yorn.cjs +628 -628
- package/package.json +2 -2
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
# C Code Review Guide
|
|
2
|
+
|
|
3
|
+
> C code review guide focused on memory safety, undefined behavior, portability, testing, and secure coding. Examples assume C11/C17.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Pointer and Buffer Safety](#pointer-and-buffer-safety)
|
|
8
|
+
- [Ownership and Resource Management](#ownership-and-resource-management)
|
|
9
|
+
- [Undefined Behavior Pitfalls](#undefined-behavior-pitfalls)
|
|
10
|
+
- [Integer Types and Overflow](#integer-types-and-overflow)
|
|
11
|
+
- [Error Handling](#error-handling)
|
|
12
|
+
- [Concurrency](#concurrency)
|
|
13
|
+
- [Macros and Preprocessor](#macros-and-preprocessor)
|
|
14
|
+
- [API Design and Const](#api-design-and-const)
|
|
15
|
+
- [Secure Coding Practices](#secure-coding-practices)
|
|
16
|
+
- [Cross-Platform Portability](#cross-platform-portability)
|
|
17
|
+
- [Testing](#testing)
|
|
18
|
+
- [Tooling and Build Checks](#tooling-and-build-checks)
|
|
19
|
+
- [Review Checklist](#review-checklist)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Pointer and Buffer Safety
|
|
24
|
+
|
|
25
|
+
### Always carry size with buffers
|
|
26
|
+
|
|
27
|
+
```c
|
|
28
|
+
// ❌ Bad: ignores destination size
|
|
29
|
+
bool copy_name(char *dst, size_t dst_size, const char *src) {
|
|
30
|
+
strcpy(dst, src);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ✅ Good: validate size and terminate
|
|
35
|
+
bool copy_name(char *dst, size_t dst_size, const char *src) {
|
|
36
|
+
size_t len = strlen(src);
|
|
37
|
+
if (len + 1 > dst_size) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
memcpy(dst, src, len + 1);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Avoid dangerous APIs
|
|
46
|
+
|
|
47
|
+
Prefer `snprintf`, `fgets`, and explicit bounds over `gets`, `strcpy`, or `sprintf`.
|
|
48
|
+
|
|
49
|
+
```c
|
|
50
|
+
// ❌ Bad: unbounded write
|
|
51
|
+
sprintf(buf, "%s", input);
|
|
52
|
+
|
|
53
|
+
// ✅ Good: bounded write
|
|
54
|
+
snprintf(buf, buf_size, "%s", input);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Use the right copy primitive
|
|
58
|
+
|
|
59
|
+
```c
|
|
60
|
+
// ❌ Bad: memcpy with overlapping regions
|
|
61
|
+
memcpy(dst, src, len);
|
|
62
|
+
|
|
63
|
+
// ✅ Good: memmove handles overlap
|
|
64
|
+
memmove(dst, src, len);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Validate pointer arguments
|
|
68
|
+
|
|
69
|
+
```c
|
|
70
|
+
// ❌ Bad: no NULL check
|
|
71
|
+
int process(char *buf, size_t len) {
|
|
72
|
+
buf[0] = '\0';
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ✅ Good: validate before use
|
|
77
|
+
int process(char *buf, size_t len) {
|
|
78
|
+
if (!buf || len == 0) {
|
|
79
|
+
return -EINVAL;
|
|
80
|
+
}
|
|
81
|
+
buf[0] = '\0';
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Beware of pointer-to-pointer pitfalls
|
|
87
|
+
|
|
88
|
+
```c
|
|
89
|
+
// ❌ Bad: caller cannot distinguish success from failure
|
|
90
|
+
void allocate(int **out) {
|
|
91
|
+
*out = malloc(sizeof(int));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ✅ Good: return status, set output only on success
|
|
95
|
+
int allocate(int **out) {
|
|
96
|
+
if (!out) return -EINVAL;
|
|
97
|
+
int *p = malloc(sizeof(int));
|
|
98
|
+
if (!p) return -ENOMEM;
|
|
99
|
+
*out = p;
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Ownership and Resource Management
|
|
107
|
+
|
|
108
|
+
### One allocation, one free
|
|
109
|
+
|
|
110
|
+
Track ownership and clean up on every error path.
|
|
111
|
+
|
|
112
|
+
```c
|
|
113
|
+
// ✅ Good: cleanup label avoids leaks
|
|
114
|
+
int load_file(const char *path) {
|
|
115
|
+
int rc = -1;
|
|
116
|
+
FILE *f = NULL;
|
|
117
|
+
char *buf = NULL;
|
|
118
|
+
|
|
119
|
+
f = fopen(path, "rb");
|
|
120
|
+
if (!f) {
|
|
121
|
+
goto cleanup;
|
|
122
|
+
}
|
|
123
|
+
buf = malloc(4096);
|
|
124
|
+
if (!buf) {
|
|
125
|
+
goto cleanup;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (fread(buf, 1, 4096, f) == 0) {
|
|
129
|
+
goto cleanup;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
rc = 0;
|
|
133
|
+
|
|
134
|
+
cleanup:
|
|
135
|
+
free(buf);
|
|
136
|
+
if (f) {
|
|
137
|
+
fclose(f);
|
|
138
|
+
}
|
|
139
|
+
return rc;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Document ownership transfer
|
|
144
|
+
|
|
145
|
+
```c
|
|
146
|
+
// ✅ Good: comment clarifies that caller takes ownership
|
|
147
|
+
// Caller must free() the returned buffer.
|
|
148
|
+
char *read_line(FILE *f);
|
|
149
|
+
|
|
150
|
+
// ✅ Good: comment clarifies that callee does NOT take ownership
|
|
151
|
+
// The function borrows `buf`; caller retains ownership.
|
|
152
|
+
int parse_header(const char *buf, size_t len, struct Header *out);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Free exactly once, set pointer to NULL
|
|
156
|
+
|
|
157
|
+
```c
|
|
158
|
+
// ❌ Bad: double free possible
|
|
159
|
+
void destroy(struct Cache *c) {
|
|
160
|
+
free(c->entries);
|
|
161
|
+
// caller might call destroy() again → double free
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ✅ Good: NULL after free prevents double free
|
|
165
|
+
void destroy(struct Cache *c) {
|
|
166
|
+
if (!c) return;
|
|
167
|
+
free(c->entries);
|
|
168
|
+
c->entries = NULL;
|
|
169
|
+
c->count = 0;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Undefined Behavior Pitfalls
|
|
176
|
+
|
|
177
|
+
### Signed integer overflow
|
|
178
|
+
|
|
179
|
+
Signed overflow is UB in C; unsigned wraps around.
|
|
180
|
+
|
|
181
|
+
```c
|
|
182
|
+
// ❌ Bad: signed overflow is UB
|
|
183
|
+
int sum = INT_MAX + 1; // undefined behavior
|
|
184
|
+
|
|
185
|
+
// ✅ Good: check before overflow
|
|
186
|
+
if (a > 0 && b > INT_MAX - a) {
|
|
187
|
+
return -EOVERFLOW;
|
|
188
|
+
}
|
|
189
|
+
int sum = a + b;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Dangling pointers
|
|
193
|
+
|
|
194
|
+
```c
|
|
195
|
+
// ❌ Bad: returning pointer to local array
|
|
196
|
+
char *greet(void) {
|
|
197
|
+
char buf[64];
|
|
198
|
+
snprintf(buf, sizeof(buf), "hello");
|
|
199
|
+
return buf; // UB: buf is gone when function returns
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ✅ Good: caller provides buffer or use static storage
|
|
203
|
+
void greet(char *out, size_t out_size) {
|
|
204
|
+
snprintf(out, out_size, "hello");
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Uninitialized variables
|
|
209
|
+
|
|
210
|
+
```c
|
|
211
|
+
// ❌ Bad: x may be anything
|
|
212
|
+
int x;
|
|
213
|
+
if (x > 0) { /* UB: reading uninitialized automatic variable */ }
|
|
214
|
+
|
|
215
|
+
// ✅ Good: always initialize
|
|
216
|
+
int x = 0;
|
|
217
|
+
if (x > 0) { /* well-defined */ }
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Sequence point violations
|
|
221
|
+
|
|
222
|
+
```c
|
|
223
|
+
// ❌ Bad: undefined — order of evaluation of operands
|
|
224
|
+
int i = 0;
|
|
225
|
+
int a[] = { i++, i++ }; // UB: two modifications without sequence point
|
|
226
|
+
|
|
227
|
+
// ❌ Bad: modification and read without sequence point
|
|
228
|
+
int j = i + i++; // UB
|
|
229
|
+
|
|
230
|
+
// ✅ Good: separate statements
|
|
231
|
+
int a0 = i++;
|
|
232
|
+
int a1 = i++;
|
|
233
|
+
int a[] = { a0, a1 };
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Strict aliasing violations
|
|
237
|
+
|
|
238
|
+
```c
|
|
239
|
+
// ❌ Bad: violates strict aliasing
|
|
240
|
+
float f = 3.14f;
|
|
241
|
+
int i = *(int *)&f; // UB
|
|
242
|
+
|
|
243
|
+
// ✅ Good: use memcpy or union (C11 allows type-punning via union)
|
|
244
|
+
int i;
|
|
245
|
+
memcpy(&i, &f, sizeof(i));
|
|
246
|
+
|
|
247
|
+
// ✅ Also acceptable in C11:
|
|
248
|
+
union { float f; int i; } u;
|
|
249
|
+
u.f = 3.14f;
|
|
250
|
+
int i = u.i;
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Shift operations
|
|
254
|
+
|
|
255
|
+
```c
|
|
256
|
+
// ❌ Bad: shift by negative or >= width is UB
|
|
257
|
+
int x = 1 << 32; // UB if int is 32-bit
|
|
258
|
+
int y = 1 << -1; // UB
|
|
259
|
+
|
|
260
|
+
// ✅ Good: validate shift amount
|
|
261
|
+
if (shift >= 0 && shift < (int)(sizeof(int) * CHAR_BIT)) {
|
|
262
|
+
int result = 1 << shift;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Integer Types and Overflow
|
|
269
|
+
|
|
270
|
+
### Avoid signed/unsigned surprises
|
|
271
|
+
|
|
272
|
+
```c
|
|
273
|
+
// ❌ Bad: negative converted to large size_t
|
|
274
|
+
int len = -1;
|
|
275
|
+
size_t n = len; // wraps to SIZE_MAX
|
|
276
|
+
|
|
277
|
+
// ✅ Good: validate before converting
|
|
278
|
+
if (len < 0) {
|
|
279
|
+
return -1;
|
|
280
|
+
}
|
|
281
|
+
size_t n = (size_t)len;
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Check for overflow in size calculations
|
|
285
|
+
|
|
286
|
+
```c
|
|
287
|
+
// ❌ Bad: potential overflow in multiplication
|
|
288
|
+
size_t bytes = count * sizeof(Item);
|
|
289
|
+
|
|
290
|
+
// ✅ Good: check before multiplying
|
|
291
|
+
if (count > SIZE_MAX / sizeof(Item)) {
|
|
292
|
+
return NULL;
|
|
293
|
+
}
|
|
294
|
+
size_t bytes = count * sizeof(Item);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Use fixed-width types for binary protocols
|
|
298
|
+
|
|
299
|
+
```c
|
|
300
|
+
// ❌ Bad: int size varies by platform
|
|
301
|
+
struct PacketHeader {
|
|
302
|
+
int type;
|
|
303
|
+
int length;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// ✅ Good: explicit widths for wire format
|
|
307
|
+
#include <stdint.h>
|
|
308
|
+
struct PacketHeader {
|
|
309
|
+
uint32_t type;
|
|
310
|
+
uint32_t length;
|
|
311
|
+
};
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Beware of implicit promotion
|
|
315
|
+
|
|
316
|
+
```c
|
|
317
|
+
// ❌ Bad: uint8_t promotes to int in arithmetic
|
|
318
|
+
uint8_t a = 200, b = 100;
|
|
319
|
+
uint8_t sum = a + b; // truncation: 300 → 44
|
|
320
|
+
|
|
321
|
+
// ✅ Good: be explicit about width
|
|
322
|
+
uint16_t sum = (uint16_t)a + (uint16_t)b; // 300
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Error Handling
|
|
328
|
+
|
|
329
|
+
### Always check return values
|
|
330
|
+
|
|
331
|
+
```c
|
|
332
|
+
// ❌ Bad: ignore errors
|
|
333
|
+
fread(buf, 1, size, f);
|
|
334
|
+
|
|
335
|
+
// ✅ Good: handle errors
|
|
336
|
+
size_t read = fread(buf, 1, size, f);
|
|
337
|
+
if (read != size && ferror(f)) {
|
|
338
|
+
return -1;
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Consistent error contracts
|
|
343
|
+
|
|
344
|
+
- Use a clear convention: 0 for success, negative for failure.
|
|
345
|
+
- Document ownership rules on success and failure.
|
|
346
|
+
- If using `errno`, set it only for actual failures.
|
|
347
|
+
|
|
348
|
+
```c
|
|
349
|
+
// ✅ Good: clear error contract with errno
|
|
350
|
+
// Returns 0 on success, -1 on failure (sets errno).
|
|
351
|
+
// On failure, *out is unchanged.
|
|
352
|
+
int parse_int(const char *s, int *out);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Avoid errno across function boundaries
|
|
356
|
+
|
|
357
|
+
```c
|
|
358
|
+
// ❌ Bad: errno may be overwritten by intermediate calls
|
|
359
|
+
errno = 0;
|
|
360
|
+
long val = strtol(s, &end, 10);
|
|
361
|
+
log_debug("parsed: %ld", val); // might change errno!
|
|
362
|
+
if (errno != 0) { /* unreliable */ }
|
|
363
|
+
|
|
364
|
+
// ✅ Good: capture errno immediately
|
|
365
|
+
errno = 0;
|
|
366
|
+
long val = strtol(s, &end, 10);
|
|
367
|
+
int saved_errno = errno;
|
|
368
|
+
log_debug("parsed: %ld", val);
|
|
369
|
+
if (saved_errno != 0) { /* reliable */ }
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Concurrency
|
|
375
|
+
|
|
376
|
+
### volatile is not synchronization
|
|
377
|
+
|
|
378
|
+
```c
|
|
379
|
+
// ❌ Bad: data race
|
|
380
|
+
volatile int stop = 0;
|
|
381
|
+
void worker(void) {
|
|
382
|
+
while (!stop) { /* ... */ }
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ✅ Good: C11 atomics
|
|
386
|
+
_Atomic int stop = 0;
|
|
387
|
+
void worker(void) {
|
|
388
|
+
while (!atomic_load(&stop)) { /* ... */ }
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Use mutexes for shared state
|
|
393
|
+
|
|
394
|
+
Protect shared data with `pthread_mutex_t` or equivalent. Avoid holding locks while doing I/O.
|
|
395
|
+
|
|
396
|
+
```c
|
|
397
|
+
// ✅ Good: mutex + RAII-style cleanup
|
|
398
|
+
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
399
|
+
static int g_counter = 0;
|
|
400
|
+
|
|
401
|
+
void increment(void) {
|
|
402
|
+
pthread_mutex_lock(&g_lock);
|
|
403
|
+
g_counter++;
|
|
404
|
+
pthread_mutex_unlock(&g_lock);
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Avoid lock ordering issues
|
|
409
|
+
|
|
410
|
+
```c
|
|
411
|
+
// ❌ Bad: inconsistent lock ordering → deadlock
|
|
412
|
+
// Thread 1: lock(A); lock(B);
|
|
413
|
+
// Thread 2: lock(B); lock(A);
|
|
414
|
+
|
|
415
|
+
// ✅ Good: always acquire locks in the same order
|
|
416
|
+
// All threads: lock(A); lock(B);
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Macros and Preprocessor
|
|
422
|
+
|
|
423
|
+
### Parenthesize arguments
|
|
424
|
+
|
|
425
|
+
```c
|
|
426
|
+
// ❌ Bad: macro with side effects
|
|
427
|
+
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
|
428
|
+
int x = MIN(i++, j++); // evaluates argument twice
|
|
429
|
+
|
|
430
|
+
// ✅ Good: static inline function
|
|
431
|
+
static inline int min_int(int a, int b) {
|
|
432
|
+
return a < b ? a : b;
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Multi-statement macros
|
|
437
|
+
|
|
438
|
+
```c
|
|
439
|
+
// ❌ Bad: breaks in if-else without braces
|
|
440
|
+
#define LOG_AND_RETURN(msg) \
|
|
441
|
+
fprintf(stderr, "%s\n", msg); \
|
|
442
|
+
return -1
|
|
443
|
+
|
|
444
|
+
// ✅ Good: do { ... } while(0) idiom
|
|
445
|
+
#define LOG_AND_RETURN(msg) do { \
|
|
446
|
+
fprintf(stderr, "%s\n", msg); \
|
|
447
|
+
return -1; \
|
|
448
|
+
} while (0)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Include guards
|
|
452
|
+
|
|
453
|
+
```c
|
|
454
|
+
// ✅ Good: traditional include guard
|
|
455
|
+
#ifndef MY_HEADER_H
|
|
456
|
+
#define MY_HEADER_H
|
|
457
|
+
// ... declarations ...
|
|
458
|
+
#endif /* MY_HEADER_H */
|
|
459
|
+
|
|
460
|
+
// ✅ Also acceptable (non-standard but widely supported):
|
|
461
|
+
#pragma once
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## API Design and Const
|
|
467
|
+
|
|
468
|
+
### Const-correctness and sizes
|
|
469
|
+
|
|
470
|
+
```c
|
|
471
|
+
// ✅ Good: explicit size and const input
|
|
472
|
+
int hash_bytes(const uint8_t *data, size_t len, uint8_t *out);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Document nullability
|
|
476
|
+
|
|
477
|
+
Clearly document whether pointers may be NULL. Prefer returning error codes instead of NULL when possible.
|
|
478
|
+
|
|
479
|
+
```c
|
|
480
|
+
// ✅ Good: document contract in the header
|
|
481
|
+
// @param name Non-NULL, NUL-terminated string.
|
|
482
|
+
// @param out Non-NULL output pointer.
|
|
483
|
+
// @return 0 on success, -EINVAL if name or out is NULL.
|
|
484
|
+
int lookup(const char *name, struct Result *out);
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Opaque types for encapsulation
|
|
488
|
+
|
|
489
|
+
```c
|
|
490
|
+
// ✅ Good: header exposes only a pointer
|
|
491
|
+
typedef struct Parser Parser;
|
|
492
|
+
|
|
493
|
+
Parser *parser_create(const char *input);
|
|
494
|
+
int parser_next(Parser *p, struct Token *out);
|
|
495
|
+
void parser_destroy(Parser *p);
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Secure Coding Practices
|
|
501
|
+
|
|
502
|
+
### CERT C: buffer overflow prevention
|
|
503
|
+
|
|
504
|
+
```c
|
|
505
|
+
// ❌ Bad: strncpy does NOT guarantee NUL termination
|
|
506
|
+
char dst[32];
|
|
507
|
+
strncpy(dst, src, sizeof(dst)); // if src >= 32 bytes, dst is not terminated!
|
|
508
|
+
|
|
509
|
+
// ✅ Good: explicit NUL termination after strncpy
|
|
510
|
+
char dst[32];
|
|
511
|
+
strncpy(dst, src, sizeof(dst) - 1);
|
|
512
|
+
dst[sizeof(dst) - 1] = '\0';
|
|
513
|
+
|
|
514
|
+
// ✅ Better: use snprintf for bounded string copy
|
|
515
|
+
char dst[32];
|
|
516
|
+
snprintf(dst, sizeof(dst), "%s", src);
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Format string vulnerability
|
|
520
|
+
|
|
521
|
+
```c
|
|
522
|
+
// ❌ Bad: user-controlled format string
|
|
523
|
+
printf(user_input); // if user_input = "%x %x %x", reads stack
|
|
524
|
+
|
|
525
|
+
// ✅ Good: always use a format literal
|
|
526
|
+
printf("%s", user_input);
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Integer overflow in allocation
|
|
530
|
+
|
|
531
|
+
```c
|
|
532
|
+
// ❌ Bad: count * size may overflow before malloc sees it
|
|
533
|
+
void *items = malloc(count * sizeof(Item));
|
|
534
|
+
|
|
535
|
+
// ✅ Good: check for overflow
|
|
536
|
+
if (count != 0 && SIZE_MAX / count < sizeof(Item)) {
|
|
537
|
+
errno = ENOMEM;
|
|
538
|
+
return NULL;
|
|
539
|
+
}
|
|
540
|
+
void *items = malloc(count * sizeof(Item));
|
|
541
|
+
|
|
542
|
+
// ✅ Also good: use calloc (checks internally)
|
|
543
|
+
Item *items = calloc(count, sizeof(Item));
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Validate external input lengths
|
|
547
|
+
|
|
548
|
+
```c
|
|
549
|
+
// ❌ Bad: trusting header-declared length
|
|
550
|
+
struct Msg { uint32_t len; char data[]; };
|
|
551
|
+
void handle(struct Msg *m) {
|
|
552
|
+
char buf[256];
|
|
553
|
+
memcpy(buf, m->data, m->len); // attacker controls m->len
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ✅ Good: validate before use
|
|
557
|
+
void handle(struct Msg *m, size_t total_size) {
|
|
558
|
+
if (m->len > total_size - sizeof(struct Msg)) {
|
|
559
|
+
return -EINVAL;
|
|
560
|
+
}
|
|
561
|
+
char buf[256];
|
|
562
|
+
if (m->len > sizeof(buf)) {
|
|
563
|
+
return -E2BIG;
|
|
564
|
+
}
|
|
565
|
+
memcpy(buf, m->data, m->len);
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Avoid TOCTOU race conditions
|
|
570
|
+
|
|
571
|
+
```c
|
|
572
|
+
// ❌ Bad: check-then-use is a race (TOCTOU)
|
|
573
|
+
if (access(path, R_OK) == 0) {
|
|
574
|
+
FILE *f = fopen(path, "r"); // file may have changed between access() and fopen()
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// ✅ Good: try and check the result
|
|
578
|
+
FILE *f = fopen(path, "r");
|
|
579
|
+
if (!f) {
|
|
580
|
+
// handle error (ENOENT, EACCES, etc.)
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Secure temporary files
|
|
585
|
+
|
|
586
|
+
```c
|
|
587
|
+
// ❌ Bad: predictable name
|
|
588
|
+
char path[] = "/tmp/myapp_XXXXXX";
|
|
589
|
+
FILE *f = fopen(path, "w"); // predictable, race condition
|
|
590
|
+
|
|
591
|
+
// ✅ Good: mkstemp creates and opens atomically
|
|
592
|
+
char tmpl[] = "/tmp/myapp_XXXXXX";
|
|
593
|
+
int fd = mkstemp(tmpl);
|
|
594
|
+
if (fd < 0) { /* handle error */ }
|
|
595
|
+
FILE *f = fdopen(fd, "w");
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
## Cross-Platform Portability
|
|
601
|
+
|
|
602
|
+
### Preprocessor conditionals best practices
|
|
603
|
+
|
|
604
|
+
```c
|
|
605
|
+
// ❌ Bad: nested #ifdef soup
|
|
606
|
+
#ifdef _WIN32
|
|
607
|
+
#ifdef _WIN64
|
|
608
|
+
// 64-bit Windows
|
|
609
|
+
#else
|
|
610
|
+
// 32-bit Windows
|
|
611
|
+
#endif
|
|
612
|
+
#else
|
|
613
|
+
#ifdef __linux__
|
|
614
|
+
// Linux
|
|
615
|
+
#endif
|
|
616
|
+
#endif
|
|
617
|
+
|
|
618
|
+
// ✅ Good: abstract behind feature macros
|
|
619
|
+
#if defined(PLATFORM_WINDOWS)
|
|
620
|
+
#include "platform_win.h"
|
|
621
|
+
#elif defined(PLATFORM_LINUX)
|
|
622
|
+
#include "platform_linux.h"
|
|
623
|
+
#elif defined(PLATFORM_MACOS)
|
|
624
|
+
#include "platform_macos.h"
|
|
625
|
+
#else
|
|
626
|
+
#error "Unsupported platform"
|
|
627
|
+
#endif
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Byte order (endianness)
|
|
631
|
+
|
|
632
|
+
```c
|
|
633
|
+
// ❌ Bad: assumes little-endian
|
|
634
|
+
uint32_t read_u32(const uint8_t *buf) {
|
|
635
|
+
return *(const uint32_t *)buf; // alignment + endianness issues
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// ✅ Good: explicit byte-order handling
|
|
639
|
+
static inline uint32_t read_u32_le(const uint8_t *buf) {
|
|
640
|
+
return (uint32_t)buf[0]
|
|
641
|
+
| ((uint32_t)buf[1] << 8)
|
|
642
|
+
| ((uint32_t)buf[2] << 16)
|
|
643
|
+
| ((uint32_t)buf[3] << 24);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
static inline uint32_t read_u32_be(const uint8_t *buf) {
|
|
647
|
+
return ((uint32_t)buf[0] << 24)
|
|
648
|
+
| ((uint32_t)buf[1] << 16)
|
|
649
|
+
| ((uint32_t)buf[2] << 8)
|
|
650
|
+
| (uint32_t)buf[3];
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Alignment-aware access
|
|
655
|
+
|
|
656
|
+
```c
|
|
657
|
+
// ❌ Bad: unaligned access is UB on many architectures
|
|
658
|
+
uint32_t val = *(const uint32_t *)ptr;
|
|
659
|
+
|
|
660
|
+
// ✅ Good: memcpy is safe for any alignment
|
|
661
|
+
uint32_t val;
|
|
662
|
+
memcpy(&val, ptr, sizeof(val));
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Avoid platform-specific extensions in portable code
|
|
666
|
+
|
|
667
|
+
```c
|
|
668
|
+
// ❌ Bad: GCC extension in shared code
|
|
669
|
+
typeof(x) y = x;
|
|
670
|
+
|
|
671
|
+
// ✅ Good: use standard C or isolate extensions
|
|
672
|
+
// In a platform-specific header:
|
|
673
|
+
#ifdef __GNUC__
|
|
674
|
+
#define TYPEOF(x) typeof(x)
|
|
675
|
+
#else
|
|
676
|
+
#define TYPEOF(x) decltype(x) /* C++23 or compiler-specific */
|
|
677
|
+
#endif
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### Use feature detection, not platform detection
|
|
681
|
+
|
|
682
|
+
```c
|
|
683
|
+
// ❌ Bad: assumes POSIX because Linux
|
|
684
|
+
#ifdef __linux__
|
|
685
|
+
#include <sys/mman.h>
|
|
686
|
+
#endif
|
|
687
|
+
|
|
688
|
+
// ✅ Good: feature test via CMake/configure
|
|
689
|
+
#ifdef HAVE_MMAP
|
|
690
|
+
#include <sys/mman.h>
|
|
691
|
+
#endif
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## Testing
|
|
697
|
+
|
|
698
|
+
### Choosing a test framework
|
|
699
|
+
|
|
700
|
+
| Framework | Use Case | Notes |
|
|
701
|
+
|-----------|----------|-------|
|
|
702
|
+
| **Unity** | Embedded / bare-metal | Single-file, no dependencies, C89 compatible |
|
|
703
|
+
| **CUnit** | Desktop / CI | Richer assertions, HTML/XML output |
|
|
704
|
+
| **CMocka** | System-level code | Mocking via function pointers, works with `setjmp`/`longjmp` |
|
|
705
|
+
|
|
706
|
+
### Basic test structure with Unity
|
|
707
|
+
|
|
708
|
+
```c
|
|
709
|
+
#include "unity.h"
|
|
710
|
+
#include "parser.h"
|
|
711
|
+
|
|
712
|
+
void setUp(void) { /* runs before each test */ }
|
|
713
|
+
void tearDown(void) { /* runs after each test */ }
|
|
714
|
+
|
|
715
|
+
void test_parse_empty_string_returns_null(void) {
|
|
716
|
+
struct Token *t = parse("");
|
|
717
|
+
TEST_ASSERT_NULL(t);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
void test_parse_valid_integer(void) {
|
|
721
|
+
struct Token *t = parse("42");
|
|
722
|
+
TEST_ASSERT_NOT_NULL(t);
|
|
723
|
+
TEST_ASSERT_EQUAL_INT(TOKEN_INT, t->type);
|
|
724
|
+
TEST_ASSERT_EQUAL_INT(42, t->value);
|
|
725
|
+
token_free(t);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
void test_parse_negative_number(void) {
|
|
729
|
+
struct Token *t = parse("-7");
|
|
730
|
+
TEST_ASSERT_NOT_NULL(t);
|
|
731
|
+
TEST_ASSERT_EQUAL_INT(-7, t->value);
|
|
732
|
+
token_free(t);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
int main(void) {
|
|
736
|
+
UNITY_BEGIN();
|
|
737
|
+
RUN_TEST(test_parse_empty_string_returns_null);
|
|
738
|
+
RUN_TEST(test_parse_valid_integer);
|
|
739
|
+
RUN_TEST(test_parse_negative_number);
|
|
740
|
+
return UNITY_END();
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
### Test isolation: mock system calls
|
|
745
|
+
|
|
746
|
+
```c
|
|
747
|
+
// ✅ Good: inject dependencies for testability
|
|
748
|
+
// Production code:
|
|
749
|
+
struct FileOps {
|
|
750
|
+
int (*read)(void *buf, size_t size, void *ctx);
|
|
751
|
+
void *ctx;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
int load_config(const struct FileOps *ops, struct Config *out);
|
|
755
|
+
|
|
756
|
+
// Test code:
|
|
757
|
+
static int mock_read(void *buf, size_t size, void *ctx) {
|
|
758
|
+
const char *data = (const char *)ctx;
|
|
759
|
+
size_t len = strlen(data);
|
|
760
|
+
if (len < size) size = len;
|
|
761
|
+
memcpy(buf, data, size);
|
|
762
|
+
return (int)size;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
void test_load_config_with_mock(void) {
|
|
766
|
+
const char *fake_data = "key=value\n";
|
|
767
|
+
struct FileOps ops = { .read = mock_read, .ctx = (void *)fake_data };
|
|
768
|
+
struct Config cfg;
|
|
769
|
+
int rc = load_config(&ops, &cfg);
|
|
770
|
+
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
771
|
+
TEST_ASSERT_EQUAL_STRING("value", cfg.key);
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### Memory leak testing with sanitizers
|
|
776
|
+
|
|
777
|
+
```bash
|
|
778
|
+
# Run tests under AddressSanitizer
|
|
779
|
+
cc -fsanitize=address -fno-omit-frame-pointer -g -o test_runner tests/*.c src/*.c
|
|
780
|
+
./test_runner
|
|
781
|
+
|
|
782
|
+
# Run tests under Valgrind
|
|
783
|
+
cc -g -O0 -o test_runner tests/*.c src/*.c
|
|
784
|
+
valgrind --leak-check=full --error-exitcode=1 ./test_runner
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
```c
|
|
788
|
+
// ✅ Good: test that error paths don't leak
|
|
789
|
+
void test_parse_invalid_frees_resources(void) {
|
|
790
|
+
// Valgrind/ASan will catch any leaks from this call
|
|
791
|
+
struct Token *t = parse("not_a_number");
|
|
792
|
+
TEST_ASSERT_NULL(t);
|
|
793
|
+
// If parse() allocated internal state and forgot to free on error,
|
|
794
|
+
// the sanitizer will report it.
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
### Test edge cases systematically
|
|
799
|
+
|
|
800
|
+
```c
|
|
801
|
+
void test_edge_cases(void) {
|
|
802
|
+
// Zero-length input
|
|
803
|
+
TEST_ASSERT_EQUAL_INT(-EINVAL, process(NULL, 0));
|
|
804
|
+
|
|
805
|
+
// Maximum valid input
|
|
806
|
+
char buf[256];
|
|
807
|
+
memset(buf, 'a', sizeof(buf) - 1);
|
|
808
|
+
buf[sizeof(buf) - 1] = '\0';
|
|
809
|
+
TEST_ASSERT_EQUAL_INT(0, process(buf, sizeof(buf) - 1));
|
|
810
|
+
|
|
811
|
+
// One byte over the limit
|
|
812
|
+
TEST_ASSERT_EQUAL_INT(-E2BIG, process(buf, sizeof(buf)));
|
|
813
|
+
}
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
---
|
|
817
|
+
|
|
818
|
+
## Tooling and Build Checks
|
|
819
|
+
|
|
820
|
+
```bash
|
|
821
|
+
# Warnings
|
|
822
|
+
clang -Wall -Wextra -Werror -Wconversion -Wshadow -std=c11 ...
|
|
823
|
+
|
|
824
|
+
# Sanitizers (debug builds)
|
|
825
|
+
clang -fsanitize=address,undefined -fno-omit-frame-pointer -g ...
|
|
826
|
+
clang -fsanitize=thread -fno-omit-frame-pointer -g ...
|
|
827
|
+
|
|
828
|
+
# Static analysis
|
|
829
|
+
clang-tidy src/*.c -- -std=c11
|
|
830
|
+
cppcheck --enable=warning,performance,portability src/
|
|
831
|
+
|
|
832
|
+
# Formatting
|
|
833
|
+
clang-format -i src/*.c include/*.h
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### CI integration checklist
|
|
837
|
+
|
|
838
|
+
```bash
|
|
839
|
+
# Typical CI pipeline for a C project
|
|
840
|
+
clang -Wall -Wextra -Werror -std=c11 -c src/*.c # compile with strict warnings
|
|
841
|
+
clang -fsanitize=address,undefined -g -o test test/*.c src/*.c # sanitizer build
|
|
842
|
+
./test # run tests
|
|
843
|
+
valgrind --leak-check=full --error-exitcode=1 ./test # memory check
|
|
844
|
+
cppcheck --error-exitcode=1 --enable=all src/ # static analysis
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
---
|
|
848
|
+
|
|
849
|
+
## Review Checklist
|
|
850
|
+
|
|
851
|
+
### Memory and UB
|
|
852
|
+
- [ ] All buffers have explicit size parameters
|
|
853
|
+
- [ ] No out-of-bounds access or pointer arithmetic past objects
|
|
854
|
+
- [ ] No use after free or uninitialized reads
|
|
855
|
+
- [ ] Signed overflow and shift rules are respected
|
|
856
|
+
- [ ] Strict aliasing rules are respected
|
|
857
|
+
- [ ] Sequence point rules are respected
|
|
858
|
+
|
|
859
|
+
### Secure Coding
|
|
860
|
+
- [ ] No format string vulnerabilities (user input never used as format)
|
|
861
|
+
- [ ] No unchecked allocation sizes (overflow in count * size)
|
|
862
|
+
- [ ] No TOCTOU races on file operations
|
|
863
|
+
- [ ] External input lengths are validated before use
|
|
864
|
+
- [ ] Temporary files use mkstemp or equivalent
|
|
865
|
+
|
|
866
|
+
### API and Design
|
|
867
|
+
- [ ] Ownership rules are documented and consistent
|
|
868
|
+
- [ ] const-correctness is applied for inputs
|
|
869
|
+
- [ ] Error contracts are clear and consistent
|
|
870
|
+
- [ ] Pointer nullability is documented
|
|
871
|
+
- [ ] Opaque types used for encapsulation
|
|
872
|
+
|
|
873
|
+
### Portability
|
|
874
|
+
- [ ] No unaligned memory access
|
|
875
|
+
- [ ] Byte order handled explicitly for wire/binary formats
|
|
876
|
+
- [ ] Fixed-width types used for binary protocols
|
|
877
|
+
- [ ] Platform-specific code isolated behind feature macros
|
|
878
|
+
|
|
879
|
+
### Concurrency
|
|
880
|
+
- [ ] No data races on shared state
|
|
881
|
+
- [ ] volatile is not used for synchronization
|
|
882
|
+
- [ ] Locks are held for minimal time
|
|
883
|
+
- [ ] Lock ordering is consistent
|
|
884
|
+
|
|
885
|
+
### Testing and Tooling
|
|
886
|
+
- [ ] Unit tests cover happy path, error paths, and edge cases
|
|
887
|
+
- [ ] Builds clean with warnings enabled (-Wall -Wextra -Werror)
|
|
888
|
+
- [ ] Sanitizers (ASan, UBSan) run on critical code paths
|
|
889
|
+
- [ ] Valgrind or ASan confirms no memory leaks
|
|
890
|
+
- [ ] Static analysis results are addressed
|