Back to flin
flin

Appels panic en production : suivi et élimination

Comment nous avons traqué et catégorisé chaque appel panic dans le codebase Rust de 186K lignes de FLIN -- 5 panics en production, 120 panics de test, et la stratégie pour les éliminer.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 7 min flin
EN/ FR/ ES
flinauditpanicserror-handlingproduction

En Rust, panic! est un crash délibéré. Le programme affiche une trace de pile et se termine. Dans le code de test, les panics sont le mécanisme standard pour les assertions -- chaque assert!, assert_eq! et unwrap() est un panic potentiel, et c'est par conception. Dans le code de production, les panics sont presque toujours des bugs. Ils transforment ce qui devrait être une erreur gérée en crash de processus, et dans un runtime de langage comme FLIN, un crash signifie que toutes les applications tournant sur ce runtime tombent.

L'audit de FLIN a catalogué chaque appel panic dans 186 252 lignes de code Rust. Le nombre brut était alarmant : plus de 800 sites de panic dans le codebase. Le nombre raffiné était rassurant : seuls 5 étaient dans des chemins de code de production. Les restants étaient des assertions de test, ce qui est exactement là où les panics ont leur place.

Cet article retrace ces cinq panics de production -- où ils vivent, pourquoi ils existent, s'ils devraient être éliminés, et à quoi ressemble la stratégie d'élimination.

Le recensement des panics

L'audit a compté les appels panic à trois niveaux : les invocations explicites de la macro panic!(), les appels .unwrap() sur les types Result et Option, et les appels .expect() qui fournissent un message avant de paniquer. Ensemble, ceux-ci forment l'ensemble complet des sites de panic dans le codebase.

Module                   Explicit panic!  .unwrap()  .expect()  Total
codegen/bytecode.rs              2            0          0         2
vm/vm.rs                        48            0          0        48
vm/renderer.rs                   9            0          0         9
parser/parser.rs               ~600           0          0       ~600
tests/**                         0          ~120        ~20      ~140
                                                                -----
TOTAL                          ~659         ~120        ~20      ~799

Les 600 panics du parser étaient tous dans le code de test (ligne 8866 et au-delà). Les 140 panics du répertoire tests étaient par définition du code de test. Il restait les modules codegen, VM et renderer avec 59 sites de panic de production à évaluer.

Les cinq panics de production

Après avoir filtré les panics qui n'étaient accessibles que par des chemins de code internes (jamais déclenchés par du code FLIN écrit par l'utilisateur), l'audit a identifié cinq panics qu'un développeur FLIN pourrait théoriquement déclencher :

PANIC-001 : dépassement du pool de constantes

rust// codegen/bytecode.rs line 1711
pub fn add_constant(&mut self, value: Value) -> u16 {
    if self.constants.len() >= u16::MAX as usize {
        panic!("Constant pool overflow: too many constants (max {})", u16::MAX);
    }
    self.constants.push(value);
    (self.constants.len() - 1) as u16
}

Un utilisateur peut-il le déclencher ? Oui, mais uniquement avec un programme FLIN extraordinairement grand -- un qui définit plus de 65 535 constantes uniques. Une application FLIN typique utilise quelques centaines de constantes. Atteindre 65 535 nécessiterait un effort délibéré.

Devrait-ce être un panic ? Possiblement oui. C'est une limite dure du format bytecode. La réponse appropriée est de refuser la compilation, ce qu'un panic fait effectivement (bien qu'un message d'erreur approprié serait mieux).

Verdict : Convertir de panic! à un CompileError::ConstantPoolOverflow qui produit un message d'erreur convivial indiquant au développeur de découper son programme en modules.

PANIC-002 : assertion de type float

rust// codegen/bytecode.rs line 2709
fn read_float_constant(&self, index: u16) -> f64 {
    match &self.constants[index as usize] {
        Value::Float(f) => *f,
        other => panic!("Expected Float constant at index {}, got {:?}", index, other),
    }
}

Un utilisateur peut-il le déclencher ? Non, dans des circonstances normales. Si cette assertion se déclenche, cela signifie que le compilateur a un bug.

Verdict : Convertir en Result pour un rapport d'erreur plus propre, mais marquer comme priorité basse puisque la condition devrait être inatteignable.

PANIC-003 à PANIC-050 : assertions de types de la VM

La VM contient 48 appels panic, presque tous suivant le même pattern :

rust// vm/vm.rs -- typical type assertion pattern
fn builtin_string_upper(&mut self) -> Result<Value, VmError> {
    let value = self.pop()?;
    let s = match value {
        Value::Object(id) => self.get_string(id)?.to_uppercase(),
        Value::Text(s) => s.to_uppercase(),
        _ => panic!("upper() requires a string argument, got {:?}", value),
    };
    Ok(Value::text(s))
}

Un utilisateur peut-il les déclencher ? Oui. Si un développeur FLIN écrit upper(42), la VM recevra une valeur entière là où elle attend une chaîne, et le panic se déclenchera.

Devrait-ce être des panics ? Absolument pas. Ce sont les panics les plus importants à éliminer. Un développeur FLIN passant le mauvais type à une fonction intégrée devrait recevoir une erreur d'exécution claire, pas un crash de processus.

Verdict : Convertir tous les 48 en retours VmError::TypeError. C'est la tâche d'élimination de panics de plus haute priorité.

rust// AFTER: proper error on type mismatch
fn builtin_string_upper(&mut self) -> Result<Value, VmError> {
    let value = self.pop()?;
    let s = self.as_string(&value)
        .map_err(|_| VmError::TypeError {
            function: "upper",
            expected: "text",
            got: value.type_name(),
        })?;
    Ok(Value::text(s.to_uppercase()))
}

PANIC-051 à PANIC-059 : assertions du moteur de rendu

Le moteur de rendu contient 9 appels panic, principalement dans l'évaluation d'expressions et la génération HTML.

Un utilisateur peut-il les déclencher ? Oui. Les expressions de templates qui s'évaluent à des types inattendus peuvent atteindre ces sites de panic.

Verdict : Convertir en dégradation gracieuse -- rendre un marqueur d'erreur visible dans la sortie HTML et journaliser un avertissement, mais ne jamais crasher le processus serveur.

La stratégie d'élimination

L'audit a proposé une stratégie à trois niveaux pour l'élimination des panics :

Niveau 1 : convertir en VmError (48 panics de la VM). Chaque assertion de type dans les gestionnaires de fonctions natives de la VM devrait retourner Result<Value, VmError> au lieu de paniquer.

Niveau 2 : convertir en dégradation gracieuse (9 panics du renderer). Les panics du renderer devraient être remplacés par un rendu de secours qui produit une sortie visible mais sans crash.

rust// Tier 2: graceful degradation in renderer
fn extract_text_content(&self, value: &Value) -> String {
    match value {
        Value::Text(s) => s.clone(),
        Value::Object(id) => {
            self.vm.get_string(*id)
                .unwrap_or_else(|_| format!("[render error: invalid object {}]", id))
        }
        Value::Int(n) => n.to_string(),
        Value::Float(f) => f.to_string(),
        Value::Bool(b) => b.to_string(),
        Value::None => String::new(),
        other => format!("[render error: unexpected {:?}]", other.type_name()),
    }
}

Niveau 3 : convertir en CompileError (2 panics du codegen). Le dépassement du pool de constantes et l'assertion de type float devraient produire des erreurs de compilation plutôt que des crashs à l'exécution.

Les 120 panics de test

Les 120 panics dans le code de test ne sont pas des bugs. Ce sont le mécanisme standard de test de Rust. Chaque .unwrap() est un site de panic. Chaque assert_eq! est un site de panic. Ce sont des utilisations correctes de panic -- ils terminent le test avec un message d'échec, ce qui est exactement ce que les assertions de test doivent faire.

Mesurer les progrès

Production panic sites:
  Start of audit:     59 (48 VM + 9 renderer + 2 codegen)
  After Tier 1:       11 (0 VM + 9 renderer + 2 codegen)
  After Tier 2:        2 (0 VM + 0 renderer + 2 codegen)
  After Tier 3:        0 (0 VM + 0 renderer + 0 codegen)

Target: zero production panics before beta release

Pourquoi zéro panic compte

Un runtime de langage est une infrastructure. Quand un développeur FLIN écrit une application web et la déploie en production, il fait confiance au fait que le runtime ne crashera sous aucune entrée que ses utilisateurs fournissent. Un panic dans le runtime n'est pas juste un bug de FLIN -- c'est une indisponibilité pour chaque application construite sur FLIN.

Zéro panic en production n'est pas un objectif théorique. C'est une propriété concrète et mesurable du binaire. Soit les appels panic! existent dans les chemins de code de production, soit ils n'existent pas. L'audit nous a donné la liste exacte. Les sessions de correction les élimineraient. Et le pipeline CI garantirait qu'ils ne reviennent jamais.


Ceci est la partie 154 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont conçu et construit un langage de programmation à partir de zéro.

Navigation de la série : - [153] Ce que l'audit nous a appris sur la construction d'un langage - [154] Appels panic en production : suivi et élimination (vous êtes ici) - [155] 93 sessions auditées en un seul passage

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Le Step Zero ne suffisait pas : comment valider un constructeur sans valider le runtime a fait tomber toutes les sessions vocales de Déblo l’heure où nous avons livré le streaming caméra temps réel

La phase 14 a livré Déblo Eyes — streaming caméra temps réel via LiveKit vers Gemini Live native audio. Le premier deploy a fait tomber toutes les sessions vocales en production en quatre-vingt-dix secondes parce que notre Step 0 avait validé le constructeur sans exercer le runtime. Le build log de comment Déblo a eu des yeux, ce qu’un pré-vol incomplet a coûté, et quels points de polish ont été livrés ou reportés.

33 min May 20, 2026
debloclaude-opus-4.7claude-codegemini-live +25
Thales & Claude deblo

Le tiret cadratin qui a tué la production : comment un slogan marketing dans un header HTTP a fait tomber le chat de Déblo pendant 24 heures

Deux jours avant la soumission App Store, tout le produit chat de Déblo s’est cassé silencieusement. Pas de spinner, pas de toast, aucune erreur dans l’UI — juste un silence radio. L’incident de 24 heures se résumait à un seul « é » dans la valeur d’un header HTTP qui levait une UnicodeEncodeError avant qu’aucune requête vers OpenRouter ne quitte le backend. Post-mortem d’une fausse hypothèse, d’une trace Sentry, et d’un fix de six lignes qui a débloqué le lancement.

30 min May 19, 2026
debloclaude-opus-4.7claude-codeincident +19
Thales & Claude deblo

Six heures, d’une page blanche à la review Apple — Comment nous avons soumis Déblo à l’App Store, en direct

Marche à marche en direct de la soumission de Déblo à l’App Store iOS en six heures : ce que les validateurs d’Apple ont rejeté (un superscript Unicode), ce que nous avons corrigé (un Promotional Text gaspillé sur des marques tierces), et les rouages de l’ASO iOS que presque tout le monde rate.

30 min May 13, 2026
debloclaude-opus-4.7claude-codeapp-store +16