📄 SKILL.md 🔒 b719b738…390add88 Se connecter pour télécharger ← Retour
---
name: detecteur-lock-order
description: Détecteur de 3 classes de risques d'interblocage sur des structures concurrentes (carte partagée type DashMap, verrou type Mutex) dans du code source Rust — réentrance sans libération, opération lourde ou verrou imbriqué sous verrou, ordre d'acquisition incohérent entre fonctions. Bibliothèque standard uniquement, zéro dépendance, zéro appel réseau.
theme: securite-souverainete
langages_cibles: rust
---

# detecteur-lock-order

## Objectif

Scanner un dossier de code source Rust et détecter 3 patrons distincts de
risque d'interblocage (deadlock) sur des structures concurrentes :

- **R1 — réentrance sans libération** : un guard obtenu via `.get(`/
  `.get_mut(`/`.entry(` sur une structure `X` reste lié à une variable
  pendant qu'un second appel (`.get(`/`.get_mut(`/`.entry(`/`.insert(`/
  `.remove(`/`.contains_key(`) sur la **même** structure `X` survient avant
  tout `drop()` explicite du premier guard — l'équivalent d'un « upgrade »
  interdit d'un verrou de lecture en écriture sur la même clé.
- **R2 — opération lourde ou verrou imbriqué sous verrou** : un guard de
  verrou classique (`.lock()`) reste lié pendant qu'une opération jugée
  lourde (hachage/dérivation de clé, I/O fichier, appel réseau bloquant) ou
  un second verrou sur un identifiant **différent** survient dans sa portée
  avant tout `drop()`.
- **R3 — ordre d'acquisition incohérent** : deux fonctions du dépôt
  verrouillent la même paire d'identifiants dans un ordre inversé — le
  scénario classique d'interblocage entre deux threads concurrents qui
  acquièrent les mêmes verrous dans des ordres opposés.

## Pourquoi ce skill existe

Un bug réel de ce type — un guard obtenu par lecture sur une carte
concurrente maintenu vivant pendant qu'un second appel sur la même carte
était effectué, sans libération explicite entre les deux — a déjà été
identifié et corrigé sur du code Rust concurrent audité par ailleurs. La
règle de fond qui en découle est simple à énoncer mais facile à violer sans
s'en rendre compte dans du code par ailleurs correct :

> Pour éviter les interblocages sur une structure concurrente : extraire
> les données nécessaires et libérer le verrou avant tout calcul lourd.
> Ne jamais tenter de « mettre à niveau » un verrou de lecture en écriture
> sur la même clé — libérer d'abord la lecture, puis demander l'écriture.
> S'assurer que tous les threads acquièrent plusieurs verrous dans le même
> ordre absolu, ou mieux, éviter de détenir plusieurs verrous à la fois.

Ce skill généralise cette règle en 3 détecteurs statiques, applicables à
n'importe quel dépôt Rust utilisant des structures concurrentes de ce type.

## Pourquoi Rust (et pas un autre langage) ?

Même famille de raisons que les 3 autres skills Rust du projet
(`linter-temps-constant`, `detecteur-async-bloquant`, `detecteur-panic-points`,
`detecteur-toctou-fichiers`) : c'est le langage du code analysé, un binaire
compilé scanne un gros répertoire `src/` en quelques secondes, et la stdlib
(y compris `std::collections::HashMap`, utilisée uniquement pour le
registre interne de R3 — jamais pour scanner du texte externe) suffit
entièrement.

**Conception** : suivi de profondeur d'accolades/parenthèses caractère par
caractère (même technique que les autres skills Rust du projet), avec
exclusion des chaînes de caractères (normales et brutes `r#"..."#`) et des
commentaires. R1/R2 détectent une liaison `let NOM = X.methode(...)` ou
`if let Some(NOM) = X.methode(...)` juste avant l'appel, puis cherchent un
second appel conflictuel dans une fenêtre bornée (jusqu'à la fin de la
fonction englobante, ou jusqu'au premier `drop(NOM)` rencontré). R3 agrège,
par fonction, la séquence des identifiants distincts verrouillés via
`.lock(` dans leur ordre de première apparition, puis compare toutes les
paires entre toutes les fonctions du dépôt scanné.

## Garanties de conception (non négociables)

- **Zéro dépendance tierce**, **zéro appel réseau**.
- **Contenu scanné traité comme donnée inerte**, jamais exécuté ni interprété.
- **Fonctions cœur 100% pures et testées isolément** : `construire_masque_code`,
  `extraire_fonctions`, `detecter_binding_avant`, `detecter_reentrance_carte`,
  `detecter_lourd_sous_verrou`, `detecter_ordre_incoherent` ne font aucune I/O.
- **Exemption ligne par ligne** via le marqueur `lock-order-ok` en
  commentaire, pour les cas où le patron détecté est délibérément sûr
  (revue manuelle effectuée).
- **`eprintln!` au lieu de `tracing`** : même raison que les autres skills
  Rust du projet — outil CLI ponctuel, pas un serveur long-running.

## Conformité aux règles du projet (verify_rules_rust.sh)

```bash
bash ../../outils/verify_rules_rust.sh --src src
```

**Résultat au 2026-07-30 : 0 violation bloquante (12 occurrences §18/§31b
initialement signalées, toutes vérifiées et confirmées faux positifs — des
chaînes de caractères utilisées comme fixtures de test contenant le texte
littéral `fn`/`Mutex`/`.lock()`/`.await`, jamais du vrai code exécutable en
dehors du module `#[cfg(test)]`, annotées `§18-ok`/`§31b-ok`) + 3
avertissements non bloquants (2× §5 déjà documentés dans les autres skills
Rust du projet, 1× §26 suggestion de style clippy non retenue — cohérence
avec le rejet de clippy du projet source, jugé trop générique).**

## Mode d'emploi

**Compiler :**

```bash
cargo build --release
```

**Lancer :**

```bash
./target/release/detecteur-lock-order <dossier_cible>
```

**Tests unitaires** (22 tests, aucun réseau, aucune I/O disque réelle) :

```bash
cargo test
```

**Résultat réel obtenu lors de la validation de ce skill** (dossier `src/`
d'un projet Rust d'environ 658 fichiers, ~5,1 secondes) : 42 signalements
R1/R2, 0 incohérence R3. Analyse manuelle d'un échantillon représentatif :
la majorité provient de l'absence de connaissance de TYPE de l'outil (voir
Limites connues) — des méthodes `.get()`/`.insert()` de mêmes noms sur des
structures **non concurrentes** (valeurs JSON désérialisées, tables de
hachage simples) sont lexicalement indiscernables d'une vraie carte
concurrente. Un sous-ensemble minoritaire touchait de vraies structures
concurrentes déclarées comme telles dans le code, mais restait dans des
fermetures/blocs internes distincts et sûrs en pratique — la fenêtre
d'analyse du guard (bornée à la fin de la fonction englobante, pas au bloc
exact) sur-approxime délibérément le risque plutôt que d'en manquer un vrai,
au prix de ce type de faux positif. 0 vraie incohérence d'ordre (R3)
trouvée sur ce dépôt.

**Prérequis :** Rust stable (testé avec rustc 1.93, édition 2024). Aucune
dépendance à installer.

## Limites connues (honnêteté de l'outil, pas de sur-promesse)

- **Aucune connaissance de type — purement lexical** : l'outil ne sait pas
  distinguer une vraie carte concurrente d'une structure exposant les mêmes
  noms de méthode (une valeur JSON désérialisée, une table de hachage
  simple, une table de base embarquée) — c'est la source principale des
  faux positifs observés lors de la validation réelle. Une revue humaine
  reste nécessaire pour confirmer que le récepteur signalé est réellement
  une structure concurrente avant d'agir sur un signalement.
- **`.lock()` sur un handle d'entrée/sortie standard** (verrouillage d'un
  flux stdin/stdout/stderr pour un accès exclusif à la console) partage la
  même syntaxe que le verrouillage d'un vrai Mutex de synchronisation —
  angle mort lexical déjà connu de ce type d'outil, qui ne peut être levé
  qu'en connaissant le type réel du récepteur.
- **Portée du guard approximée par la fin de la fonction englobante**, pas
  par le bloc/fermeture exact où la liaison a été créée — un guard
  réellement droppé à la fin d'un bloc interne (fermeture, `if`, etc.) avant
  un second appel plus loin dans la même fonction peut donc être signalé à
  tort. Choix assumé : sur-approximation délibérée (jamais manquer un vrai
  risque au prix d'un signalement en trop), documentée honnêtement plutôt
  que masquée.
- **R3 corrèle par identifiant TEXTUEL du récepteur**, même limite assumée
  que les autres skills du projet fondés sur une corrélation lexicale — un
  alias (`let b = a;`) casse la corrélation (faux négatif), et à l'inverse
  deux identifiants de même nom référençant des structures logiquement
  différentes dans deux fonctions distinctes pourraient être comparés à
  tort (faux positif).
- **Un guard renommé via un pattern complexe** (destructuration, tuple,
  fermeture capturant implicitement) peut échapper à R1/R2 — seuls les
  patrons `let NOM = ...`/`let mut NOM = ...`/`if let Some(NOM) = ...` sont
  reconnus.
- Ne remplace pas une revue de sécurité humaine ni des tests de concurrence
  réels (`loom`, tests de charge sous contention) — outil de dégrossissage
  rapide et reproductible, pas une preuve d'absence d'interblocage.
8.9 Ko BLAKE3 : b719b738…390add88