Le comportement de JS fonctionnant sur `Java SE 8 Nashorn` a soudainement changé

Que voulez-vous dire?

Il aurait fallu beaucoup de temps pour écrire les détails de l'affaire, alors j'écrirai d'abord la conclusion.

Dans la version java" 1.8.0_92 ", des correctifs ont été ajoutés à Nashorn, ce qui peut changer le comportement de JavaScript fonctionnant sur Nashorn en mettant à jour Java. Les changements qui semblent avoir le plus d'impact (et je suis vraiment dedans) sont:

*** Lorsque le type long Java et le type int sont passés à JavaScript exécuté par Nashorn, ils étaient traités comme du type Number dans le passé, mais après modification, ils sont traités comme du type ʻObject` ***

Il y a probablement des modèles dans le code qui sont affectés par ce changement (changements de comportement), mais une comparaison typique (en fait je suis accro) fait des comparaisons d'égalité strictes. Je vais.

// JavaScript
function hoge(arg) { // <-arg est un objet qui stocke les arguments passés de Java
  if arg.getLongValue() === 0 return 0 // <-Ici arg.getLongValue()Est 0L sur Java===Le résultat de est faux et else est exécuté
  else arg.getLongValue / 2;
}; 

Contre-mesures

Il existe deux contre-mesures possibles pour les cas ci-dessus.

  1. Modifiez le côté JavaScript. Arrêtez la comparaison d'égalité stricte (===) et passez à la comparaison d'égalité (==) pour la partie où la valeur numérique (long, int) transmise depuis Java et le type JS Number sont comparées. ..
  2. Modifiez le côté Java. Convertissez les nombres (long, int) à transmettre à JavaScript en type double (seul le type double est traité comme type Number par JS).

Celui qui doit être adopté dépend de la situation, mais si vous voulez modifier le côté Java, vous devez construire, déployer et redémarrer, donc dans de nombreux cas, je pense que 1. est plus facile.


Ci-dessous, l'histoire dans laquelle j'étais vraiment

Qu'est-ce que Nashorn en premier lieu?

Un moteur JavaScript pour exécuter JavaScript en Java. Dans «Java SE 6-7», il y avait un moteur JavaScript Mozilla appelé «Rhino», mais dans «Java SE 8», il a été remplacé par «Nashorn» d'Oracle. Comparé à Rhino, il est compatible avec ʻECMAScript-262`, et la vitesse d'exécution est grandement améliorée, ce qui semble être une grande différence.

Qu'est-ce qui vous rend heureux de pouvoir exécuter JS en Java?

Je pense qu'il existe de nombreuses utilisations de Nashorn, mais par exemple, il est nécessaire d'activer le «remplacement dynamique de la logique». Dans un certain processus de calcul, j'aimerais faire quelque chose comme un test AB de logique pour voir quel type de changement se produira dans les performances, et si un changement positif est observé, passer progressivement à une nouvelle logique. Je vois.

Je pense que l'explication est abstraite et difficile à comprendre, mais en bref

// Java
Object a: = doSomething() // step 1

Object b = executeDynamicLogic(a) // step 2 <--Je veux le remplacer facilement!

return b // step 3

L'ambiance est comme ça.

Dans l'exemple ci-dessus, passez l'argument «a» au code de script extrait de la base de données, etc. et exécutez-le, et stockez le résultat dans la variable «b». .. .. Je veux pouvoir changer le comportement de l'application simplement en METTRE À JOUR le script stocké dans la base de données. C'est comme traiter la logique comme des données.

Si vous pouvez atteindre l'objectif, vous pouvez utiliser Python ou Ruby au lieu de JavaScript, mais dans mon cas,

D'un tel point de vue, je me souviens que j'ai décidé d'utiliser JavaScript.

Et l'incident s'est produit

À un moment donné, JavaScript fonctionnant sur Nashorn, qui n'avait jamais eu de problème auparavant, a commencé à donner une erreur.

Quelle était la cause

Quand j'ai commencé à enquêter, j'ai naturellement suspecté des changements dans mon code, mais je n'ai fait aucun changement autour de Nashorn et le script en cours d'exécution a également été transmis au script. Il n'y avait rien de suspect dans les données. Quand j'ai commencé à penser que je ne comprenais pas cela ...

** "Oh, peut-être que j'ai mis à jour la version Java ..." **

Un rayon de lumière. À ce stade, j'ai commencé à douter de la possibilité que "Nashorn ne soit pas affecté par certains changements dans la mise à jour?", Et quand je l'ai recherché, je l'ai trouvé très rapidement.

JDK-8144020 | Remove long as an internal numeric type

En résumé,

*** Jusqu'à présent, «int» et «long» étaient également traités en interne comme «double» et mappés sur «nombre» JS (en Java, la définition de «nombre» de type ECMA est satisfaite. (Parce qu'il n'est que «double») Cependant, lorsque vous traitez «long» comme «double», certains problèmes surviennent en raison de l'ajout d'une précision supplémentaire de 53 bits, alors arrêtez ce processus de conversion interne YO! *** ***

.. .. .. Vraiment? Donc, quand je vérifie l'environnement d'exploitation

$ java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

Hmmm. Il est supérieur à "1.8.0_92". .. ..

Le code qui a causé le problème

Le code à l'origine du problème ressemblait à ceci:

// JavaScript
function hoge(arg) { // <-arg est un objet qui stocke les arguments passés de Java
  if arg.getLongValue1() === 0 return 0
  else arg.getValue1() / arg.getLongValue2(); 
};

Le fait est que nous essayons d'éviter la division zéro, mais le problème est

arg.getValue1() === 0

C'est la partie de. Comparaison d'égalité exacte (vérifiez si les types et les valeurs correspondent) entre ʻarg.getValue1 () , qui est le type long dans le monde Java, et 0, qui est le type Number` dans le monde JS. )Faire.

Pour Nashorn jusqu'à Java8u91, si ʻarg.getValue1 () == 0L est true du côté Java, cette expression renvoie true. Cependant, depuis «Java8u92», le résultat est «faux». La raison en est que les valeurs des côtés gauche et droit de ** ===sont0`, mais les types sont différents **.

Comportement sous Java 8u91

//Arg du côté Java.getValue1()Si est 0L
arg.getValue1() === 0 // arg.getValue1()Est un nombre(0)Et le résultat est vrai

Comportement sous Java 8u92

//Arg du côté Java.getValue1()Si est 0L
arg.getValue1() === 0 // arg.getValue1()Est un objet(0)Et le résultat est faux

En d'autres termes, même si le type de la valeur passée du côté Java n'a pas changé avec long, lorsque la version Java passe de 8u91 ou moins à 8u92 ou plus, le type du côté JS mappé par Nashorn seraNumber. Il passe deà ʻObject. En conséquence, dans le code ci-dessus, le résultat retourné par l'opération d'égalité exacte ===change, la division par zéro que l'on essayait d'éviter est inévitable etPositiveInfinity` est mélangée avec la valeur au milieu du calcul. Après cela, tous les calculs sont devenus des valeurs inattendues, les résultats ont été renvoyés du côté Java et une erreur s'est produite.

Leçon

J'ai senti qu'il était important d'être prêt à douter sans le croire de la tête simplement parce que c'était officiel.

Recommended Posts

Le comportement de JS fonctionnant sur `Java SE 8 Nashorn` a soudainement changé
[Java] [Spring] Tester le comportement de l'enregistreur
Vérifiez le comportement de Java Intrinsic Locks avec bpftrace
Je n'ai pas vraiment compris le comportement de Java Scanner et .nextLine ()
20190803_Java & k8s sur Azure L'histoire d'aller au festival
Les débutants en Java ont brièvement résumé le comportement de Array et ArrayList
Parlons de l'expérience passagère de Java SE 8 Programmer II
L'histoire de ne pas connaître le comportement de String en passant par Java
Méthode d'enquête lorsque le processeur du serveur exécutant java est lourd
À propos du comportement de ruby Hash # ==
[Édition Java] Histoire de la sérialisation
Remarque sur le chemin de request.getRequestDispatcher
Histoire du passage de Java Gold SE8
L'origine des expressions Java lambda
Qu'est-ce qui n'était pas une utilisation équitable du détournement d'API Java sur Android?
Comment obtenir le chemin absolu d'un répertoire s'exécutant en Java