javaz : les sources !
J'ai décidé de me bouger le cul et de livrer les sources de javaz (compilateur Java pour Zam, le bytecode Caml). Il n'y a à l'heure actuelle pas de licence, pas de warning, pas d'instructions, rien
Vous pouvez compiler (nécessite d'avoir de quoi compiler du caml sur votre machine...) et tester, et un certain nombre de jeux de test sont déjà inclus et testable (avec "make tree", "make peano", etc...).
Je ferai probablement un petit billet sur les entrailles de la bête le jour où j'aurai le temps et si ça intéresse quelqu'un. Mais pour l'instant, c'est livré "as is".
Java : variable arity
Je ne sais plus où j'ai vu ça pour la première fois, mais en tout cas depuis Java 1.5 on peut faire des méthodes à arité variable, et j'ai trouvé la façon de faire plutôt pas mal.
Version C/C++
En C++, pour faire varié l'arité, on a en gros 2 façon de faire (en tout cas que j'utilise) :
void truc(int bidule, int blabla, float machin=0.0f, int ok = 0);
Ici, c'est particulier, car au final c'est juste qu'on donne des arguments par défaut. En Java, on peut obtenir le même résultat en surchargeant la méthode avec moins d'arguments, et en la faisant appeler l'autre méthode en mettant soi-même les valeurs par défaut.
int printf ( const char * format, ... );
Je prends volontairement printf, puisque c'est le meilleur exemple du genre, mais voilà, on peut aussi utiliser les "va_args". J'ai toujours trouvé ça un peu galère à utiliser, et la version Java me parait au final bien plus propre...
Version Java
public void myMethod(int arg0, int arg1, Object... objects){...}
La syntaxe est proche, sauf que cette fois-ci on précise le type des paramètres (en mettant Object on est super large, mais on met ce qu'on veut...). Ce qu'on récupère, au final, c'est juste un tableau "objects" contenant tous les Object passés en argument. Simple, efficace, on peut connaitre leur nombre, on peut boucler dessus avec une boucle foreach, et on peut aussi utiliser Arrays.asList(objects) si on veut en faire une liste.
Je m'en suis servi au boulot justement à un endroit où on voulait passer l'équivalent d'une liste d'éléments à utiliser dans une interface graphique. Je ne sais pas si niveau performance c'est bien (a priori pas forcément, car d'un côté on créé un array qu'il faut remplir, et de l'autre je converti l'array en liste), mais en tout cas au niveau code je pense qu'on a gagné en lisibilité. Ca évite de créer une liste, d'ajouter tous les éléments dedans, puis de passer cette liste en argument. Là on fait juste ça :
setKeyboards(KeyboardType.Alpha, KeyboardType.Num, KeyboardType.Symbols);
Bien ou pas ? Je dirais que ce genre d'ajout à un langage relève plus du sucre syntaxique qu'autre chose, puisque a priori il n'y a aucune nouvelle "fonctionnalité", juste une façon plus pratique d'écrire. Mais ça reste une façon simple et efficace d'enrichir un langage ![]()
(si ça se trouve dans 2 semaines je ferai un billet sur pourquoi c'est mal d'utiliser les méthodes à arité variable, mais pas grave ^^)
javaz : coming soon
Je fais un article rapide, la suite arrivera d'ici quelques jours ^^ Juste pour dire qu'hier avec Benoit (mon binome sur ce projet) on a rendu le premier jet de javaz, notre projet de compilateur Java pour zam (bytecode caml).
Le projet n'est pas encore très abouti, mais permet déjà de tester quelques trucs. On a de l'héritage, des appels de méthodes, des champs, après il manque des conneries genre les méthodes statiques, les génériques, les tableaux, et autres bétises.
Pour les tests, on a mis au point 2 exemples, un qui cherche des nombres premiers, et un exemple où on représente les entiers par des objets foireux : un objet de type Zero qui représente 0 (WOW !), et un objet de type Succ qui possède un objet (Zero ou Succ) et qui a pour valeur 1 + cet objet. Donc en gros pour représenter les calculs, c'est le bordel, et pour faire de simples additions ça coute super cher, mais au moins pour faire du bench sur les appels de méthodes et les accès aux champs, c'est marrant ^^
Je posterai tout ça avec quelques instructions d'utilisation demain. Mais pour résumer, en gros, niveau performances on tourne entre 3.5 et 10 fois plus lent que la JVM, ce qui est correct compte tenu du fait que le bytecode caml est exécuté dans une VM sans JIT, contrairement à la JVM
Projet intéressant, la suite demain !
Performances en Java

J'ai souvent des a priori sur Java et sur ce qu'il faut faire ou éviter. J'avais notamment en tête qu'il vaut mieux éviter d'utiliser de bons gros codes bien lourd avec de la récurrence à gogo, et en fait l'autre jour j'ai lu un test qui tend à prouver le contraire...
Quantifying Recursion on the Java Platform
Je ne vais pas faire un gros résumé de l'article puisqu'il est déjà court et très clair, mais en gros, j'en retiens 2 choses :
- Un code récursif peut être plus rapide que son homologue itératif, en Java
- Quoi qu'on fasse, il est important de savoir comment tourne le code derrière... Je pense notamment à l'utilisation du "foreach" (oui, je sais, c'est un simple "for" en Java), qui n'est pas forcément gratuite...
A propos de ce for spécial, même s'il rend le code particulièrement clair et lisible, ce n'est pas forcément gratuit pour autant. Cet article met ce phénomène en avant, car entre une boucle for "à la main" (avec un compteur et tout) et une boucle for "jolie", la consommation de mémoire est très différente, ainsi que le temps d'exécution.
Bon, ça ne va par contre pas dire qu'il vaut mieux éviter ce for, car en fait il peut y avoir des cas où ça sera l'inverse : ce qui se passe en pratique est que Java génère pour ces boucles des Iterators. Dans le cas d'un ArrayList, où on peut accéder à n'importe quel élément de la liste en O(1), ce n'est pas vraiment utile, voire même contre-productif. Par contre, si on utilise un autre type de liste, et là je pense tout particulièrement aux listes chainées, alors l'Iterator sera bien plus rapide et efficace (puisque l'accès à l'élément suivant est rapide) que si on utilise le "children.get(i)", qui lui va enclencher un parcours de la liste jusqu'à arriver au "i"ème élément à chaque fois...
J'ai pu constater ce genre de phénomène "à la con" à la fac l'année dernière, où les étudiants qui ne codent qu'en Java ont pris la fâcheuse tendance d'utiliser tout et n'importe quoi sans chercher à savoir en quoi ça consiste réellement. Quand ils ont voulu coder un Huffman en Java, ils ont eu un gain de l'ordre de 50x à un moment en changeant simplement le type de List utilisé !!!
Méfiance, donc
Evaluation non paresseuse en Java
A ma grande surprise, il est possible en Java d'utiliser les opérateurs binaires '&' et '|' sur des booléens, alors que j'étais persuadés qu'ils étaient limités aux opérations binaires sur les entiers...
En fait, Ces symboles font presque la même chose que '&&' et '||', mais sans évaluation paresseuse. Etudions ce que fait le code suivant :
if (checkFoo() && checkBar()){
...
}
Ici, si checkFoo retour FALSE, il n'est pas utile d'évalué checkBar, puisqu'on sait par avance que le résultat du test dans le if sera FALSE. Par évaluation paresseuse, checkBar n'est donc pas appelé... Or, que se passe-t-il si dans checkBar on avait un effet de bord ? Car comme celui-ci n'est pas effectué, cela pourrait changer l'execution du programme.
Avec le code suivant, on passe outre l'évaluation paresseuse :
if (checkFoo() & checkBar()){
...
}
Le résultat du test sera inchangé, mais par contre cette fois-ci checkBar sera appelée.
Je pense que dans l'ensemble il est rare d'avoir besoin de ce genre de subtilité, et j'utilise toujours l'évaluation paresseuse (qui, par définition, est sensé être plus rapide...) ; mais on ne sait jamais, ça pourrait servir un jour
A noter qu'on peut par contre trouver des cas où il est nécessaire d'avoir une évaluation paresseuse pour que le programme tourne correctement. Voici un en C/C++, et un autre en Java :
my_type *p = getThing();
if ((p != NULL) && p->bool_value){
...
}
MyObj obj = getObject();
if ((obj != null) && obj.isAlive()){
...
}
Dans les 2 cas, l'évaluation paresseuse va permettre de garantir que l'expression de droite ne sera évaluée que si la première est validée. Dans la version C, on a donc la garanti que le pointeur sera valide (ou en tout cas non null), et de même avec la référence en Java.
Javaz : Compiler du Java en bytecode Caml
Dans le cadre de la matière "Typage et Polymorphisme", à la fac, on avait un choix de sujet (projets ou articles), et avec mon binôme aucun ne nous plaisait. Du coup il a voulu qu'on propose au prof notre propre sujet : compiler des sources Java en bytecode Caml...
Bon, le prof n'était pas trop content, mais il a fini par accepter le projet. On a déjà un petit truc basique qui tourne, et c'est assez intéressant. Le tout est écrit en OCaml, comme ça c'est l'occasion pour moi de m'y remettre un peu (après 1 ou 2 ans sans y avoir touché), et il faut dire que ça se prête particulièrement bien à la tache.
Les différentes étapes du projet sont relativement classiques :
1. Conversion en Ast
On commence donc par convertir les sources en Ast (Arbre de syntaxe abstraite), grâce à Yacc et Lex. En gros, c'est un parcours du fichier source, et on transforme tous les mots-clés et autres éléments repérés sous forme d'arbre.
Ainsi, l'expression (2 + 3) devient un noeud "opération + ", qui a "2" et "3" comme noeuds. Et donc par extension, 2 + 3 * 4 devient "*" avec comme fils gauche "+" (ayant lui-même les fils "2" et "3") et à droite "4". C'est une façon de parser qui revient à mettre le parenthésage implicite qui va bien, et qui permet en fait de calculer le résultat très facilement et récursivement (on applique le méthode de calcul sur le fils gauche, puis sur le fils droit, et fait l'opération correspondante sur le résultat).
La transformation en Ast permet déjà de vérifier si la syntaxe est valide selon la norme...
2. Typage de l'Ast
Une fois qu'on a cet Ast, on va chercher à le typer, ce qui va permettre au passage de vérifier la validité de tout un tas de trucs, et d'appliquer les casts implicites. En Java, le typage est explicite, donc le typeur n'a pas un énorme travail à faire pour "déduire" des types, il se contente de regarder ce qu'il a un peu partout et de valider ou pas. On a ainsi plusieurs opérations d'effectuées :
- Typage de tous les noeuds de l'Ast : à la fin on obtient un Ast typé, correspondant au fait à tous les noeuds de l'Ast original (plus quelques nouveaux) auquel on ajoute le type résultant. Si on a un noeud "+" qui a comme fils un entier et un floatant, le noeud aura donc comme type "float".
- Insertion des casts implicites : à chaque fois qu'on a 2 types différents qui se "rencontrent", on regarde comment caster l'un en l'autre de façon implicite. Ainsi, si on a une opération "+" qui prend un entier et un string, on va rajouter le cast de l'entier en string pour faire une concaténation de chaines... De même, si on affecte un entier à une variable qui est de type floatant, on va rajouter le cast. Et en fait on se rend compte qu'on a pas mal de cas, notamment à cause de tous les types d'entiers (byte, char, short, int, long).
- Vérification de la validité des variables : pendant le parcours, on va "noter" toutes les variables locales que l'on rencontre (ainsi que les champs et paramètres, en fait), et donc quand on utilise une variable dans une méthode, on va tout de suite pouvoir vérifier si elle existe. Le cas échéant, on s'arrête avec un message d'erreur de type "Undeclared Variable XXX". Si elle existe, on va en profiter pour transformer le nom de la variable en une information un peu plus pratique le générateur de code pourra manipuler plus facilement : dans notre cas, on a choisi de représenter une variable locale par un couple d'entiers, le premier représentant le bloc relatif au courant auquel elle appartient, et le second le numéro de la variable dans ce bloc (par ordre de déclaration). Il est donc très facile pour le générateur de code de retrouver la variable dans la pile : soit c'est dans le bloc courant, donc on récupère sa position par rapport au pointeur de pile, soit c'est dans un bloc parent, et dans ce cas il suffit de connaitre le pointeur de pile correspondant à ce parent pour retrouver la position de la variable dans la pile.
- Vérification des méthodes : on va appliquer le même principe aux méthodes (pas tout à fait fini, mais c'est en cours), vérifier que la méthode existe bien (et est accessible) dans un objet de cette classe, etc...
- Transformation des boucles for en boucles while, histoire de ne pas se trainer des cas particuliers tout le temps. En cours de route on est tombé sur une petite subtilité : on ne peut pas se contenter de fusioner les instructions du corps avec le code à exécuter à la fin de chaque tour de boucle, car si on fait un "continue" au milieu du bloc, il faut bien exécuter les dernières instructions... Du coup la boucle while possède en fait 2 blocs : un bloc d'instruction pour le corps de la boucle, et une expression à évaluer après l'évaluation du bloc... Pour transformer la boucle for en boucle while, on créé juste un premier bloc d'instructions comprenant l'initialisation de la boucle, ainsi que la boucle while résultante. Ce qui permet de gérer automatiquement les variables déclarées à la volée dans la boucle for
Il reste encore pas mal de boulot au niveau du typeur, et c'est notamment lui qui va implémenter une première version de la gestion de surcharges...
3. Interprétation de l'Ast typé (optionnel)
Cette étape est un peu superflue et sert essentiellement pour des raisons de tests, car elle permet de tester le résultat de l'arbre typé en l'exécutant... Comme on tourne dans du code maison, on peut rajouter des affichages de debug un peu partout pour voir ce qui se passe. Et comme l'Ast est typé, l'interpréteur peut en fait vérifier les types à tout moment, sachant que jusqu'à maintenant la seule fois ou un type a été explosé (à savoir, une variable de type boolean contenant la valeur entière "0"), c'est quand j'ai fait péter le pointeur de pile ^^. C'est bien pratique pour repérer les conneries au niveau du typeur, comme des index de variables locales mal mis.
Les performances de l'interpréteur sont bien entendu catastrophique, puisqu'on ne fait que se balader dans un arbre dans un peu tous les sens, en récupérant les fils, puis en remontant, etc...
4. Génération de code
Pour l'instant, seul mon binôme s'est intéressé à cette partie, donc je n'ai pas encore mis les mains dedans. Mais en gros le principe est de prendre chacune des instructions/expressions de l'Ast typé, et de la traduire en code machine correspondante. On écrit ici du bytecode Caml, mais dans le fond c'est un peu la même démarche que quand on génère de l'assembleur (sauf que là ça serait un assembleur de plus haut niveau avec des instructions sympas ^^). Donc voilà, on pousse des valeurs sur la pile, on met le bytecode correspondant à l'addition, etc... C'est un peu long et fastidieux pour l'instant, mais quand on va arriver à la génération du code pour les objets ça devrait être assez sympa :p
Mots de la fin
Le projet n'est pas encore terminé, mais en quelques jours on a déjà bien avancé. C'est vraiment très instructif, car on en apprend à la fois sur Java (avec les spécifications à la con... c'est comme ça que j'ai appris qu'on pouvait mettre un label aux blocs pour ensuite appliquer ce label sur les continue/break !), sur Caml (car on a déjà plusieurs milliers de lignes de code, donc ça fait une bonne base de travail ^^), sur le typage en général, etc... En fait c'est une manière assez rapide de synthétiser tout un ensemble de cours que j'ai eu à la fac depuis 1 an (et d'autres en cours), donc c'est top !
Après, quant à savoir si le projet pourrait avoir un jour un intérêt autre que le délire de l'avoir fait, on est en train de se poser la question, et a priori... non ! Mais techniquement, ça marche déjà : on a fait plusieurs tests, notamment un qui consiste à calculer les nombres premiers dans les 10 000 premiers entiers, et on obtient bien le résultat voulu
Java : normaliser un String
Dernièrement je fais un ensemble de classes "dictionnaire" en Java pour l'utilisation dans des livres électroniques, et histoire de pouvoir facilement comparer ce qu'on tape avec les mots de la base, il est important que tout soit normalisé... On doit donc retirer tout ce qui est accent, en particulier, et ce n'est pas forcément très intuitif à faire quand on ne l'a jamais fait...
For heureusement, en Java on dispose déjà de tous les outils nécessaires pour gérer cela. Le code que j'utilise effectue en gros 2 passes :
- Une première pour tout mettre en majuscules (ça simplifie la recherche par la suite). Il suffit d'utiliser mla méthode toUppercase sur la chaine en question.
- Une deuxième pour retirer les accents. J'utilise pour se faire la méthode suivante :
public static String removeDiacriticalMarks(String string) {
return Normalizer.normalize(string, Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
}
Avec ça, on récupère tout simplement la chaîne privée de tous ses accents, et on est tranquille !
Unsigned Byte en Java ?

L'autre jour, j'ai eu besoin de lire un tableau d'octets qui étaient en non signés... On converti donc les données en bytearray Java qui va bien, et là, quand j'affiche les valeurs : -86, -126, etc... Et oui, en Java un byte est signé ! Et sauf erreur de ma part (après quelques courtes recherches), pas moyen d'avoir de bytes non signés...
La solution existe pourtant, mais je ne la trouve ni élégante, ni pratique : il suffit d'utiliser :
bytearray[i]&0xFF
En effet, pour les opérations les bytes sont castés en int, et avec le ET binaire, on récupère une valeur entre 0 et 255. Du coup ça marche super... Mais ça reste un poil lourd, car au lieu d'avoir un vrai beau type, partout où on veut utiliser des octets non signés, il faut faire cette manip...
Java : classe interne et syntaxe sympa
Un truc que j'aime bien en Java est la possibilité de faire des classes internes, mais surtout de les déclarer à la volée, comme un porc, en ajoutant à l'endroit où on les créé les méthodes à implémenter... En gros, ce genre de chose :
class MyClass
{
...
public void init(){
truc.setListener(new Listener(){
public void onListenerAction(int action){
...
}
});
}
...
}
Ca évite quand même de créer des micro-classes un peu partout, et puis comme ça la classe interne a quand même accès aux champs de la classe d'origine, donc c'est pratique.
Et puis comme je suis un peu un barbare, je me suis demandé comment on faisait si depuis la méthode onListenerAction on voulait utiliser un champs de MyClass qui porte le même nom qu'un des arguments... C'est con, puisque de toute façon dans ce cas il suffit de renommer l'argument, mais pas grave, j'ai quand même tester.
Normalement, on ferait "this.myField", mais comme le this se réfère à la classe interne, ça ne marche pas... Au début, je me suis dit que ça devait être comme en C++, on refile le nom de la classe d'où vient le champs, et ça suffit pour le retrouver. Donc j'ai testé ça :
class MyClass
{
private int action;
...
public void init(){
truc.setListener(new Listener(){
public void onListenerAction(int action){
MyClass.action = action;
}
});
}
...
}
Sauf que... ca ne marche pas ! Et le message d'erreur est simple : ce champs n'est pas statique, donc on ne peut pas l'utiliser directement sur MyClass... Jusque là, c'est logique, mais alors comment on fait ?
En fait, ce qu'on veut c'est l'instance de la classe MyClass depuis laquelle on a été appelé. Et c'est là que ça devient fun, car du coup on fait "MyClass.this".
Donc au final, on se retrouve à faire
public void onListenerAction(int action){
MyClass.this.action = action;
}
Et voilà le travail ! (pour l'histoire, en fait en vrai l'exemple du boulot c'était avec un appel de méthode car la classe MyClass implémentait aussi l'interface Listener pour d'obscures raisons, et qu'on avait besoin de centraliser plusieurs Listener dedans.)
Je pense qu'il doit y avoir d'autres façon de faire, comme faire une variable locale finale de type MyClass qui pointe sur "this", et l'utiliser en lieu de place de "MyClass.this", mais c'est tellement moins classe
Java 7 : faut pas être pressé…

Dans un précédent billet, je parlais de quelques évolutions qui devraient voir le jour dans Java 7. Toute la question est donc de savoir quand exactement cette nouvelle release sortira...
Sur le blog de Mark Reinhold (via developpez.com), on apprend que la version 7 ne devrait pas sortir tout de suite. Enfin, pour être plus précis, 2 options sont possibles :
=> Sortir une version JDK7 mi-2011, mais amputée de pas mal des nouveautés prévues (dont l'ensemble des trucs sympas dont j'avais parlé), et donc un JDK8 mi-2012 avec le package complet.
=> Sortir le JDK7 mi-2012...
Dans les 2 cas, c'est plutôt nase ^^ Tant pis !
Empilement d’appels…
Dans le reader qu'on fait, quand je veux ajouter une fonctionnalité (ou juste une méthode à la con) dans la couche du reader, voilà ce qu'il faut ajouter (j'ai mis en rouge les méthodes C/C++, en orange les méthodes Java "génériques", et en vert la partie Android)
- La méthode dans l'objet C++ qui s'occupe de faire le rendu (normal)
- La méthode virtuelle dans la classe abstraite de type Reader
- La méthode JNI qui appelle la méthode sur un objet de type Reader
- La méthode Java dans la classe Reader qui utilise le code JNI
- La méthode Java dans l'interface IReader (pour que tout le monde puisse voir la méthode précédemment ajoutée...)
- La méthode Java dans la classe Presenter (car on n'accède pas directement au Reader)
- La méthode qui utilise la méthode du Presenter dans le code, côté Android (enfin !)
Du coup quand je fais ça je réfléchis bien à si j'ai plusieurs méthodes à ajouter, car... La partie en rouge est sous Eclipse sous Linux (et se compile en ligne de commande), la partie en orange sous Netbeans sous Linux, et la partie en vert sous Eclipse sous MacOSX ^^.
JNI : trop d’objets tue l’objet
Un charmant petit plantage sur Android tout à l'heure, alors que le même code marchait très bien sur d'autres JVM (celle de Sun Oracle, ainsi que JamVM). Heureusement, le message d'erreur de Dalvik est très explicite, et m'indique que côté JNI je dépasse le nombre autorisé de local refs (qui est de seulement 512).
En fait dans ce bout de code je génère une table des matières en Java à partir d'une table des matières en C++. Le souci, c'est que celle-ci possède genre 300 entrées, et qu'en Java pour chaque entrée je dois créer 1 objet et 1 String, soit 2 objets au total. 300*2 = 600 > 512... Game Over !
La solution est simple, et les explications sur le pourquoi du comment très clairement exposées sur le site de Sun :
(*env)->DeleteLocalRef(env, jstr);
Une fois que l'objet créé a été refilé comme référence à un autre objet Java, on n'a plus vraiment besoin de garder la référence locale (qui in fine ne sert qu'à s'assurer que le GC ne va pas venir libérer des objets qu'on vient d'allouer côté C...), et donc on peut supprimer la LocalRef.
Java 7 et ses évolutions

Ce matin j'ai lu un article très intéressant sur Java 7 et les pistes envisagées pour l'évolution du langage. Je ne vais pas tout commenter, mais ça m'a fait plaisir de voir que ça évolue dans le bon sens.
Java est réputé comme étant un langage très (trop) verbeux. Il y a une chose que l'on peut faire "facilement" en C, et de façon moins directe en Java : des pointeurs sur fonction. En Java on se retrouve en fait à utiliser une classe implémentant l'interface "Runnable" (ou autre), et ça devient assez vite moche à lire. Parmi les introductions de Java 7, on pourra donc trouver le type SAM (Single Abstract Method), qui représente en fait une classe n'ayant qu'une méthode abstraite, et les expressions lambda. Je ne vais pas repomper tout l'article car c'est super bien expliqué, mais voici résumé en 1 ligne de code ce que ça pourrait donner de sympa :
Comparator<String> comparator = #(a, b) { a.compareToIgnoreCase(b) };
La syntaxe est quand même super compacte, très lisible, et on se paie même le luxe au passage de ne pas préciser les types, car ceux-ci peuvent être déterminés automagiquement (comme on dit chez nous).
Je ne vais pas détailler le reste des potentiels ajouts/modifications, mais ils permettent tous soit de faire de nouvelles choses (pas forcément révolutionnaires, mais qui rendent le développement plus agréable et évitent d'avoir à faire des hacks foireux), soit de faire la même chose qu'avnat mais de façon plus simple et lisible. En somme, que du bon
Pour ceux que ça intéresse, je vous refile donc le lien : http://blog.developpez.com/adiguba/p9232/java/java7-projet-lambda/
JVM : plusieurs implémentations = plusieurs problèmes
L'open source, c'est génial. Java, c'est fantastique, c'est la liberté, tout le monde peut faire ce qu'il veut avec. Mais si chacun est libre de faire sa JVM, ça veut aussi dire qu'il existe un risque accru de problèmes.
C'était déjà le cas avec le Java sur mobiles, à l'époque des "vieux" Nokia et compagnie : chaque téléphone avait son lot de bugs inclus dans la JVM, et les développeurs devaient faire avec. Du coup, au lieu d'avoir une seule version d'un code qui marche partout (puisque c'était le principe du Java...), il fallait à l'époque être capable de patcher les différentes versions pour les différents téléphones... Pratique !
Cette semaine on a eu ce genre de problèmes, mais en utilisant des trucs standards : la JVM JamVM (qu'on utilise pour sa performance et sa compacité en mémoire) et la version JDBC/JNI de SQLite : avec la JVM de Sun, ça marche parfaitement, tandis qu'avec JamVM, on a un segmentation fault. C'est le genre d'erreur un peu gênant quand on fait du Java, puisque ce n'est pas sensé se produire ^^ Mais c'est l'utilisation de JNI, et donc de code natif, qui introduit ce genre de soucis.
Heureusement (façon de parler), le bug ne se produisait pas sur toutes les requêtes, et on s'est rendu compte qu'il ne se produisait qu'en cas de requête qui aurait du renvoyer un String à null... En mettant les mains dans le code de la couche JNI, en remontant les appels successifs, on a rapidement cerné le soucis :
JNIEXPORT jstring JNICALL Java_org_sqlite_NativeDB_column_1text(JNIEnv *env, jobject this, jlong stmt, jint col)
{
return (*env)->NewStringUTF(env, (const char*)sqlite3_column_text(toref(stmt), col));
}
Ce petit bout de code fait le pont entre la partie purement C de sqlite (qui renvoie donc un (char *)) et la partie JNI (qui doit renvoyer un String) : on créé un String à partir du (char *) en utilisant les routines fournies par la JVM... Et c'est là que tout s'explique : la JVM de Sun autorise qu'on lui file un pointeur à NULL, et renvoie alors null comme String, alors que JamVM ne l'autorise pas.
On a été assez étonné de tomber sur un problème comme ça (à la fois du côté de JamVM qui ne gère pas ça comme la JVM de Sun, et du côté de sqlitejdbc, qui pourrait jouer la sécurité en faisant la vérification à la main). Le patch a été facile, il suffit de rajouter un petit if...
Moralité ? On ne peut pas toujours faire confiance aux libs et outils qu'on utilise, parfois il faut savoir s'en méfier et mettre le nez dedans
JamVM ARM bugs
J'avais parlé dans un précédent billet d'un souci qu'on avait, avec une exception levée dans une méthode toString, pour convertir un long en String... Et bien le problème est de retour, et en fait ça vient de la machine virtuelle qu'on utilise : JamVM.
Le soucis est relativement con, et clairement énoncé dans le bugtracker : "unary minus (-) in jamvm 1.5.4 fails for long on ARM". Or, comment marche la méthode toString ? Si on a un nombre positif, on converti, et si on a un nombre négatif, on affiche '-' au début, on le rend positif, et on le converti. Du coup, la méthode toString va lever une exception et planter lamentablement dès qu'on va convertir un long négatif, puisqu'on n'est pas en mesure de le rendre positif...
Super !