Files
agent-skills/skills/code-review-excellence/reference/c.md
Jason Woltje d9bcdc4a8d feat: Initial agent-skills repo — 4 adapted skills for Mosaic Stack
Skills included:
- pr-reviewer: Adapted for Gitea/GitHub via platform-aware scripts
  (dropped fetch_pr_data.py and add_inline_comment.py, kept generate_review_files.py)
- code-review-excellence: Methodology and checklists (React, TS, Python, etc.)
- vercel-react-best-practices: 57 rules for React/Next.js performance
- tailwind-design-system: Tailwind CSS v4 patterns, CVA, design tokens

New shell scripts added to ~/.claude/scripts/git/:
- pr-diff.sh: Get PR diff (GitHub gh / Gitea API)
- pr-metadata.sh: Get PR metadata as normalized JSON

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:03:39 -06:00

5.5 KiB

C Code Review Guide

C code review guide focused on memory safety, undefined behavior, and portability. Examples assume C11.

Table of Contents


Pointer and Buffer Safety

Always carry size with buffers

// ? Bad: ignores destination size
bool copy_name(char *dst, size_t dst_size, const char *src) {
    strcpy(dst, src);
    return true;
}

// ? Good: validate size and terminate
bool copy_name(char *dst, size_t dst_size, const char *src) {
    size_t len = strlen(src);
    if (len + 1 > dst_size) {
        return false;
    }
    memcpy(dst, src, len + 1);
    return true;
}

Avoid dangerous APIs

Prefer snprintf, fgets, and explicit bounds over gets, strcpy, or sprintf.

// ? Bad: unbounded write
sprintf(buf, "%s", input);

// ? Good: bounded write
snprintf(buf, buf_size, "%s", input);

Use the right copy primitive

// ? Bad: memcpy with overlapping regions
memcpy(dst, src, len);

// ? Good: memmove handles overlap
memmove(dst, src, len);

Ownership and Resource Management

One allocation, one free

Track ownership and clean up on every error path.

// ? Good: cleanup label avoids leaks
int load_file(const char *path) {
    int rc = -1;
    FILE *f = NULL;
    char *buf = NULL;

    f = fopen(path, "rb");
    if (!f) {
        goto cleanup;
    }
    buf = malloc(4096);
    if (!buf) {
        goto cleanup;
    }

    if (fread(buf, 1, 4096, f) == 0) {
        goto cleanup;
    }

    rc = 0;

cleanup:
    free(buf);
    if (f) {
        fclose(f);
    }
    return rc;
}

Undefined Behavior Pitfalls

Common UB patterns

// ? Bad: use after free
char *p = malloc(10);
free(p);
p[0] = 'a';

// ? Bad: uninitialized read
int x;
if (x > 0) { /* UB */ }

// ? Bad: signed overflow
int sum = a + b;

Avoid pointer arithmetic past the object

// ? Bad: pointer past the end then dereference
int arr[4];
int *p = arr + 4;
int v = *p; // UB

Integer Types and Overflow

Avoid signed/unsigned surprises

// ? Bad: negative converted to large size_t
int len = -1;
size_t n = len;

// ? Good: validate before converting
if (len < 0) {
    return -1;
}
size_t n = (size_t)len;

Check for overflow in size calculations

// ? Bad: potential overflow in multiplication
size_t bytes = count * sizeof(Item);

// ? Good: check before multiplying
if (count > SIZE_MAX / sizeof(Item)) {
    return NULL;
}
size_t bytes = count * sizeof(Item);

Error Handling

Always check return values

// ? Bad: ignore errors
fread(buf, 1, size, f);

// ? Good: handle errors
size_t read = fread(buf, 1, size, f);
if (read != size && ferror(f)) {
    return -1;
}

Consistent error contracts

  • Use a clear convention: 0 for success, negative for failure.
  • Document ownership rules on success and failure.
  • If using errno, set it only for actual failures.

Concurrency

volatile is not synchronization

// ? Bad: data race
volatile int stop = 0;
void worker(void) {
    while (!stop) { /* ... */ }
}

// ? Good: C11 atomics
_Atomic int stop = 0;
void worker(void) {
    while (!atomic_load(&stop)) { /* ... */ }
}

Use mutexes for shared state

Protect shared data with pthread_mutex_t or equivalent. Avoid holding locks while doing I/O.


Macros and Preprocessor

Parenthesize arguments

// ? Bad: macro with side effects
#define MIN(a, b) ((a) < (b) ? (a) : (b))
int x = MIN(i++, j++);

// ? Good: static inline function
static inline int min_int(int a, int b) {
    return a < b ? a : b;
}

API Design and Const

Const-correctness and sizes

// ? Good: explicit size and const input
int hash_bytes(const uint8_t *data, size_t len, uint8_t *out);

Document nullability

Clearly document whether pointers may be NULL. Prefer returning error codes instead of NULL when possible.


Tooling and Build Checks

# Warnings
clang -Wall -Wextra -Werror -Wconversion -Wshadow -std=c11 ...

# Sanitizers (debug builds)
clang -fsanitize=address,undefined -fno-omit-frame-pointer -g ...
clang -fsanitize=thread -fno-omit-frame-pointer -g ...

# Static analysis
clang-tidy src/*.c -- -std=c11
cppcheck --enable=warning,performance,portability src/

# Formatting
clang-format -i src/*.c include/*.h

Review Checklist

Memory and UB

  • All buffers have explicit size parameters
  • No out-of-bounds access or pointer arithmetic past objects
  • No use after free or uninitialized reads
  • Signed overflow and shift rules are respected

API and Design

  • Ownership rules are documented and consistent
  • const-correctness is applied for inputs
  • Error contracts are clear and consistent

Concurrency

  • No data races on shared state
  • volatile is not used for synchronization
  • Locks are held for minimal time

Tooling and Tests

  • Builds clean with warnings enabled
  • Sanitizers run on critical code paths
  • Static analysis results are addressed