cours La mémoire : qui possède quoi 5/5

Cache corrompu

Épreuve finale : un mini cache cache trois bugs mémoire différents. Dirigez le junior pour tous les corriger.

Leçon

Épreuve finale : trois bugs, trois symptômes

Ce mini cache copie clés et valeurs, évince l'entrée la plus ancienne quand il est plein, et peut écrire son contenu dans le buffer d'un appelant. Il cache trois bugs indépendants :

  1. une fuite
  2. un use-after-free
  3. un off-by-one dans une vérification de bornes

Chacun se détecte par un signal différent : un test qui échoue, le sanitizer, ou un test caché.

Votre mission

Travaillez comme un tech lead. Un bug par instruction. Après chaque patch, relancez les tests et le sanitizer et lisez ce qui a changé. N'avancez pas tant que le rapport n'est pas différent, et n'acceptez pas un patch que vous ne savez pas expliquer.

À surveiller

  • Chaque slot possède deux allocations.
  • cache_get renvoie un pointeur valable seulement jusqu'au prochain cache_put. Et si l'appelant le redonne tel quel ?
  • snprintf renvoie la longueur qu'il aurait écrite. Que signifie le cas où elle égale la place restante ?

Essayez cet exercice

Créez un compte gratuit pour dire au junior IA quoi changer, lancer les tests et le vérificateur mémoire, et obtenir un code de complétion vérifiable.

Code de départ
cache.c
#include "cache.h"#include <stdio.h>#include <stdlib.h>#include <string.h> void cache_init(Cache *c) {    c->count = 0;    memset(c->slots, 0, sizeof c->slots);} const char *cache_get(const Cache *c, const char *key) {    for (size_t i = 0; i < c->count; i++) {        if (strcmp(c->slots[i].key, key) == 0) return c->slots[i].val;    }    return NULL;} void cache_put(Cache *c, const char *key, const char *val) {    for (size_t i = 0; i < c->count; i++) {        if (strcmp(c->slots[i].key, key) == 0) {            free(c->slots[i].val);            c->slots[i].val = strdup(val);            return;        }    }    if (c->count == CACHE_CAP) {        free(c->slots[0].key);        for (size_t i = 1; i < c->count; i++) c->slots[i - 1] = c->slots[i];        c->count--;    }    c->slots[c->count].key = strdup(key);    c->slots[c->count].val = strdup(val);    c->count++;} size_t cache_dump(const Cache *c, char *buf, size_t n) {    if (n == 0) return 0;    size_t used = 0;    for (size_t i = 0; i < c->count; i++) {        int w = snprintf(buf + used, n - used, "%s=%s;", c->slots[i].key, c->slots[i].val);        if (w < 0 || (size_t)w > n - used) {            used = n - 1;            break;        }        used += (size_t)w;    }    buf[used] = '\0';    return used;} void cache_free(Cache *c) {    for (size_t i = 0; i < c->count; i++) {        free(c->slots[i].key);        free(c->slots[i].val);    }    c->count = 0;}
cache.h
#ifndef CACHE_H#define CACHE_H#include <stddef.h> #define CACHE_CAP 3 typedef struct {    char *key;    char *val;} Slot; typedef struct {    Slot slots[CACHE_CAP];    size_t count;} Cache; void cache_init(Cache *c); /* Stores private copies of key and val. Updates the value if the key exists. * When the cache is full the OLDEST entry is evicted. */void cache_put(Cache *c, const char *key, const char *val); /* Returns the value for key, or NULL. The pointer stays valid until the next cache_put. */const char *cache_get(const Cache *c, const char *key); /* Writes "k=v;k=v;" into buf. The result is ALWAYS NUL-terminated and truncated to fit in n bytes. * Returns the number of characters written, not counting the NUL. */size_t cache_dump(const Cache *c, char *buf, size_t n); /* Frees everything the cache owns. */void cache_free(Cache *c);#endif