10 déc. 2015

Quand la sonde Nudge APM corrige JBoss dynamiquement




Cet article décrit un problème que nous avons rencontré sur la sonde Nudge APM, problème un peu particulier car il était provoqué par un bug ... de JBoss ! Et la meilleure solution que nous avons trouvée n'était autre que de fixer JBoss lui-même.

Tout commence lorsqu'un de nos utilisateurs rencontre une exception sur le comportement de la sonde dans un contexte de JBoss 7.


Une obscure ArrayIndexOutOfBoundsException est provoquée lors de la lecture du bytecode par la librairie ASM.

La classe qui pose problème est rapidement repérée, il s'agit d'un proxy généré dynamiquement par JBoss en présence de l'annotation @Singleton.

Après un travail un peu laborieux, le problème parvient à être reproduit à petite échelle sur un programme de test qui ne fait qu'une seule chose : injecter le bytecode de la classe qui pose problème dans le parser ASM.
Cela permet de dédouaner la sonde Nudge APM et désigne deux coupables possibles : le générateur de classe de JBoss ou le parser ASM.

S'en suit une seconde étape non moins laborieuse d'exécution du test en debug pas à pas ... Il en ressort finalement que c'est JBoss qui produit une classe non conforme à la spec Java.

Pour les détails sur cette anomalie, retrouvez son explication en fin d'article.

Cette erreur est néanmoins inoffensive normalement, en effet l'information erronée est inutile pour le JRE lors du chargement de cette classe. C'est donc un problème invisible tant qu'on n'est pas amené à faire passer la classe par le parser ASM.

Une fois arrivés à ce constat, voici les solutions que nous avons envisagées :
  1. adapter ASM pour accepter ce cas de figure (cela revient à être plus permissif que la spec Java) => Contrainte : cela implique la nécessité de maintenir un fork de la librairie ASM.
  2. demander à JBoss de corriger le problème => Contrainte : quand bien même JBoss fixerait ce problème (qui ne pose pas de souci sauf pour la sonde Nudge APM), on n'a aucune idée du délai d'implémentation de ce correctif ; par ailleurs, il nous est difficile d'exiger de nos utilisateurs qu'il procèdent à un upgrade de leur JBoss.
  3. fixer le bug de JBoss dynamiquement grace à la sonde.

Le problème a été identifié sur la version 1.0.1 du module classfilewriter. La version 1.0.3 est validée comme corrigeant ce problème.
La classe incriminée est très petite. Nous avons regardé en détails les différences entre les versions 1.0.1 et 1.0.3.
Bref, nous sommes rapidement arrivés à la conclusion que la solution 3 était la meilleure.
Il y avait plusieurs manières d'effectuer ce fix et nous avons opté pour la plus simple que les circonstances nous permettaient : la substitution pure et simple du bytecode de la version défectueuse par le bytecode de la version correcte.

Le problème est maintenant résolu et les classes dynamiquement générées par JBoss sont maintenant conformes à la spec Java lorsque l'agent Nudge APM est activé.

Voici en détail l'anomalie dans JBoss :


Selon la spec de structuration des classfile, les annotations devraient être décrites avec un type_index pointant sur une constante de type CONSTANT_Utf8_info.
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16
The value of the type_index item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure representing a field descriptor representing the annotation type corresponding to the annotation represented by this annotation structure.
Or, le module classfilewriter de JBoss 7 (version 1.0.1 par défaut) utilisé pour créer les classes de proxy pour certaines annotations comme par exemple @Singleton déclare les annotations avec un type_index correspondant à une constante de type CONSTANT_Class_info.
Et la librairie ASM n'est pas permissive à ce non respect de la spéc Java.

Voici la classe en erreur (en version 1.0.1) : https://github.com/jbossas/jboss-classfilewriter/blob/1.0.1.Final/src/main/java/org/jboss/classfilewriter/annotations/ClassAnnotation.java (ligne 48)
this.typeIndex = constPool.addClassEntry(type);

Elle a été corrigée à partir de la version 1.0.3: https://github.com/jbossas/jboss-classfilewriter/blob/1.0.3.Final/src/main/java/org/jboss/classfilewriter/annotations/ClassAnnotation.java (ligne 44)


Ecrit par Thomas Arnaud cofondateur et directeur technique de Nudge. Adore instrumenter du bytecode à la volée et est toujours prêt à défendre ses portails sur Ingress.