IntelliJ IDEA - Intégration de l'analyse des flux de données Java et du débogueur

Par :
Tagir Valeev

jeu, 19/03/2020 - 15:47

Java Dataflow Analysis (DFA) est capable de déduire des faits concernant votre programme : exceptions possibles, conditions toujours vraies/toujours fausses, et plus encore. Il effectue une interprétation abstraite du code source, ce qui lui permet de recueillir des informations sur l'exécution du code avant que celui-ci ne soit exécuté. Cependant, il ne sait presque rien des inputs apportés au code. Enfin, techniquement, si le paramètre de la méthode est annoté comme @NotNull, l'analyse se fie à cette annotation et suppose que null ne peut pas apparaître ici, mais ce n'est qu'une infime partie de l'information.

Par ailleurs, nous avons le débogueur. Lorsqu'il est stoppé à un point d'arrêt, il sait à peu près tout sur le programme : la valeur exacte de chaque variable et champ, le contenu de chaque tableau, et ainsi de suite. Ainsi, le débogueur connaît le présent et la DFA peut prédire l'avenir. Pourquoi ne pas transmettre les données présentes à la DFA et voir ce qui se passera ?

Nous avons justement conçu cette amélioration expérimentale pour le débogueur Java. Si vous déboguez la source Java et restez sur le point d'arrêt, l'analyse du flux de données est effectuée sur la base de l'état actuel du programme et voit ce qui se passera ensuite. Cela n'affecte pas l'état de votre processus et rien n'est exécuté à l'intérieur de la session de débogage. Vous pouvez considérer cela comme une sorte de fork virtuel de votre processus.

Quels sont les avantages de ce procédé ?

L'avantage principal est l'évaluation des conditions. Cela ressemble à ceci :

Vous voyez les indications “= false” et “= true” ? Elles sont ajoutées par la DFA. Vous savez maintenant que le premier if ne sera pas exécuté, alors que le second le sera. Il semble que le débogueur se contente d'évaluer l'expression qu'il voit dans le code et l'affiche. Mais ce n'est pas exactement le cas. Vous voyez la ligne if (exception == null) return false; ? Le débogueur sait désormais que exception est null, donc exception == null est vrai. Toutefois, la DFA sait également que la variable exception peut être réaffectée au moment où cette condition est exécutée. Par conséquent, il ne tire pas de conclusions hâtives, mais affiche les résultats uniquement pour les conditions qui devraient avoir la valeur affichée lorsqu'elles sont effectivement exécutées. Il sait que la valeur d'une variable peut changer et parfois il sait même précisément comment elle va changer :

Ici, size n'a pas encore été calculé, mais la DFA sait que comme cst n'est ni Long ni Double, size sera initialisé à 1, donc size == 1 est vrai sur la ligne suivante. Un autre exemple :

La valeur actuelle de steps est de zéro, donc si vous évaluez steps % 128 == 0 alors ce sera true. Cependant, la DFA indique false. Pourquoi ? Parce que steps++ est sur le point d'être exécuté.

La DFA peut également vous avertir de certaines exceptions connues avant qu'elles ne se produisent réellement (contrairement à la DFA statique, elle ne signale pas “les NPE possibles”, seulement ceux dont elle est sûr), par exemple :

Comme conf est null et ne change clairement pas avant l'opérateur de déréférence, un NPE est inévitable ici et la DFA vous le dit. En étant informé à l'avance, vous évitez de vous retrouver finalement bloqué et d'avoir à revenir plus haut dans la trame sans comprendre pourquoi cela s'est produit. Voici un autre exemple :

Nous ne le voyons pas dans l'éditeur, mais nous savons que preConf.insnIndex est négatif à ce stade. Ainsi, nous savons déjà que dfsTree.loopEnters[insnIndex] échouera avec AIOOBE. De plus, la DFA signale actuellement ClassCastException, ArrayStoreException, faisant passer un null garanti pour un paramètre annoté comme @NotNull et une violation du contrat de méthode :

À l'avenir, nous envisageons d'afficher davantage de valeurs (actuellement, seules les valeurs true et false sont affichées) et de griser les blocs de code (par exemple, les branches if) qui ne seront pas executés.

Il est à noter que la DFA peut parfois se tromper car elle n'exécute pas réellement votre programme. Pour rendre l'analyse plus utile, elle fait quelques hypothèses raisonnables mais pas toujours vraies :

  • Elle estime que les champs finaux ne changent jamais.
  • Elle fait confiance aux contrats de méthode (par exemple, si la valeur de retour @NotNull est écrite, elle suppose que la méthode ne redeviendra pas null).
  • Elle considère que le champ non volatile qui est lu dans le thread actuel n'est jamais modifié dans un autre thread, au moins jusqu'à ce qu'un point de synchronisation se produise (par exemple, le démarrage d'un bloc synchronized {}).
  • Elle estime que les méthodes annotées comme "pure" ne modifient pas les champs visibles, les tableaux, etc.
  • Et ainsi de suite.

Même si cela devrait être rare, il peut donc arriver qu'une mauvaise indication soit affichée.

Cette fonctionnalité est disponible à partir de l'EAP v2020.1. Si vous n'aimez pas cette fonctionnalité, vous pouvez la désactiver en décochant l'option Predict future condition values… dans Preferences / Settings | Build, Execution, Deployment | Debugger | Data Views | Java :

Dans ce cas, n'hésitez pas à nous faire savoir ce qui ne vous a pas plu. De plus, vous pouvez désactiver temporairement la DFA pour la session de débogage en cours en faisant un clic droit sur n'importe quelle indication affichée :

Vos retours d'expérience sont les bienvenus. N'hésitez pas à nous laisser vos commentaires dans ce ticket YouTrack.

Bon développement !

A propos de l'auteur

Tagir Valeev
jetBrains