L'hypothèse « durée de vie du Logger vis-à-vis des événements postés » est FAUSSE.
Ce n'est pas un usage après libération : c'est un déréférencement de nullptr, et
la garde censée l'empêcher interroge un ensemble différent de celui qu'elle
protège. Correctif d'une ligne.
ÉTABLI, avec les symboles 1.15.2 (identifiants de build vérifiés) :
· #0 libnymea.so.1 + 0x14ea24 → Logger::log(...), logger.cpp:55
· #1 libnymea-core.so.1+0x1edae6 → la lambda de ThingManagerImplementation::
executeAction connectée à finished
· rdi = 0x0 au signal. Dans l'ABI SysV AMD64, rdi porte le premier argument,
donc `this`. Logger::log a été appelée sur un pointeur NUL — un usage après
libération donnerait une adresse quelconque, pas zéro.
LE MÉCANISME. La lambda fait `m_actionLoggers.value(clé)->log(...)`, et
QHash::value() rend nullptr sur une clé absente. Sa garde est
`thing->loggedActionTypeIds().contains(...)` — la liste DU THING, pas la TABLE
des journaliseurs. Au retrait d'un Thing, unregisterActionLogger vide la table
pendant que la liste du Thing reste intacte. La connexion prend `this` (le
ThingManager) pour contexte, donc un `finished` déjà posté survit au retrait :
garde vraie, table vide, nullptr déréférencé.
C'est notre défaut récurrent, dans du code amont : UN GARDE-FOU QUI INTERROGE LE
MAUVAIS OBJET. Il a l'air de protéger l'accès qui suit et n'a aucun rapport avec
lui — d'où son invisibilité à la relecture.
Et ce n'est pas limité aux tests : toute suppression de Thing pendant qu'une
action est en vol ouvre la même fenêtre en production, qu'une action longue
élargit.
SYMBOLISÉ SANS RIEN INSTALLER, et sans privilèges : `apt-get download` puis
`dpkg-deb -x`, et `addr2line` sur les fichiers extraits. Noté dans le rapport,
parce que c'est aussi la voie qui ne touche pas la machine. gdb, lui, ne peut pas
produire de pile ici : le binaire de test et le plugin ont été reconstruits depuis
le plantage, donc leurs identifiants ne correspondent plus au vidage. La
résolution par offset dans les bibliothèques reste valide, et suffit.
RÉSERVE ÉCRITE : les symboles sont ceux de 1.15.2, mais le code cité est lu dans
l'arbre 1.16.0, seule source locale. Les deux symboles s'y retrouvent à
l'identique ; la formulation exacte du correctif reste à vérifier sur la branche.
LEÇON GRAVÉE (AGENTS.md, à côté de la règle des identités) : entre deux instants
du MÊME fuseau, la soustraction rend la durée d'horloge murale, jamais la durée
réelle — donc un contrôle qui cherche les journées de 23 ou 25 h avec cette
arithmétique rend 24 h partout et conclut qu'elles n'existent pas. Trois de mes
contrôles s'y sont trompés dans ce lot, sur la question même que le champ existe
pour régler.
La parade générale est la vraie leçon : l'attendu d'un test ne se calcule pas avec
les primitives du code testé. L'oracle du test de frontière vient de hors Qt ; le
re-dériver avec QDateTime aurait refermé l'identité sur elle-même — la faute que
la règle voisine traque, retrouvée un cran plus bas, non plus dans l'assertion
mais dans la fabrication de sa valeur attendue.