The behavior of JS running on `Java SE 8 Nashorn` suddenly changed

What do you mean?

It would have been a long time to write the details of the matter, so I will write the conclusion first.

In java version" 1.8.0_92 ", some modifications have been made to Nashorn, which may change the behavior of JavaScript running on Nashorn by updating Java. The changes that seem to have the most impact (and I'm really into) are:

*** When Java long type and int type are passed to JavaScript executed by Nashorn, they were treated as Number type in the past, but after modification, they are treated as ʻObject` type ***

There are likely to be some patterns in the code that will be affected by this change (behavior will change), but a typical (actually I'm addicted to) one is doing an exact equality comparison. I will.

// JavaScript
function hoge(arg) { // <-arg is an object that stores the arguments passed from Java
  if arg.getLongValue() === 0 return 0 // <-Here arg.getLongValue()Is 0L on Java===The result of is false and else is executed
  else arg.getLongValue / 2;
}; 

Countermeasures

There are two possible countermeasures for the above cases.

  1. Modify the JavaScript side. Stop strict equality comparison (===) and change to equality comparison (==) for the part where the numerical value (long, int) passed from Java and the Number type of JS are compared. ..
  2. Modify the Java side. Cast numbers (long, int) to be passed to JavaScript to double type (only double type is treated as Number type in JS).

Which one should be adopted depends on the situation, but if you want to modify the Java side, you need to build, deploy, and restart, so in many cases, I think that 1. is easier.


Below, the story that I was really into

What is Nashorn in the first place?

A JavaScript engine for executing JavaScript in Java. In Java SE 6-7, there was a JavaScript engine made by Mozilla called Rhino, but in Java SE 8 this was replaced by Nashorn made by Oracle. Compared to Rhino, it is compliant with ʻECMAScript-262`, and the execution speed is greatly improved, which seems to be a big difference.

What are you really happy about running JS in Java?

I think there are many uses for Nashorn, but for example, there is a need to enable dynamic replacement of logic. In a certain calculation process, I would like to do something like an AB test of logic to see what kind of change will occur in performance, and if a positive change is seen, gradually move to new logic. I see.

I think the explanation is abstract and difficult to understand, but in short

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

Object b = executeDynamicLogic(a) // step 2 <--I want to replace it easily!

return b // step 3

The atmosphere is like this.

In the above example, pass the argument ʻato the script code fetched from DB etc. and execute it, and store the result in the variableb`. .. .. I want to be able to change the behavior of the application just by UPDATE the script stored in the DB. It is an image that treats logic as data.

If you can achieve the purpose, you can use Python or Ruby instead of JavaScript, but in my case,

--The official module of Java SE --Good execution performance --Population ratio around (JS> Python> Ruby)

From such a point of view, I remember that I decided to use JavaScript.

And the incident happened

At one point, JavaScript running on Nashorn, which had never had a problem before, started to give an error.

What was the cause

When I first started investigating, I naturally suspected a change in my code, but I didn't make any changes around Nashorn, and the script being executed is also passed to the script. There was nothing suspicious about the data. When I started thinking that I didn't understand this ...

** "Oh, maybe I upgraded the Java version ..." **

A ray of light. At this point, I began to doubt the possibility that "Isn't Nashorn affected by some changes in the update?", And when I looked it up, I found it very quickly.

JDK-8144020 | Remove long as an internal numeric type

In summary,

*** Until now, ʻintandlong were also treated internally as doubleand mapped to JSnumber (in Java, the ECMA-like definition of numberis satisfied. (Because it is onlydouble) However, when treating longasdouble`, some problems occur due to the addition of extra precision of 53 bits, so stop this internal conversion process YO! *** ***

.. .. .. Really? So, when you check the operating environment

$ 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. It is higher than 1.8.0_92. .. ..

The code that caused the problem

The code that caused the problem was something like this:

// JavaScript
function hoge(arg) { // <-arg is an object that stores the arguments passed from Java
  if arg.getLongValue1() === 0 return 0
  else arg.getValue1() / arg.getLongValue2(); 
};

The point is that we are trying to avoid division by zero, but the problem is

arg.getValue1() === 0

It is the part of. Exact equality comparison (check if both types and values match) between ʻarg.getValue1 (), which is the long type in the Java world, and 0, which is the literal type, which is the Number type in the JS world. )doing.

For Nashorn up to Java8u91, if ʻarg.getValue1 () == 0L is trueon the Java side, this expression returnstrue. However, since Java8u92, the result is false. The reason is that the values on the left and right sides of ** ===are0`, but the types are different **.

Behavior on Java 8u91

//Arg on Java side.getValue1()If is 0L
arg.getValue1() === 0 // arg.getValue1()Is Number(0)And the result is true

Behavior on Java 8u92

//Arg on Java side.getValue1()If is 0L
arg.getValue1() === 0 // arg.getValue1()Is Object(0)And the result is false

In other words, even if the type of the value passed from the Java side has not changed with long, when the Java version goes up from 8u91 or less to 8u92 or more, the type on the JS side mapped by Nashorn will beNumber. It changes fromto ʻObject. As a result, in the above code, the result returned by the exact equality operation ===changes, the division by zero that was trying to avoid is unavoidable, andPositiveInfinity` is mixed in with the value in the middle of calculation. After that, all the calculations became unintended values, the results were returned to the Java side, and an error occurred.

Lesson

I felt that it was important to be prepared to doubt without believing it from the head just because it was official.

Recommended Posts

The behavior of JS running on `Java SE 8 Nashorn` suddenly changed
[Java] [Spring] Test the behavior of the logger
I touched on the new features of Java 15
Check the behavior of Java Intrinsic Locks with bpftrace
I didn't understand the behavior of Java Scanner and .nextLine ().
20190803_Java & k8s on Azure The story of going to the festival
Java beginners briefly summarized the behavior of Array and ArrayList
Let's talk about the passing experience of Java SE 8 Programmer II
The story of not knowing the behavior of String by passing Java by reference
Investigation method when the CPU of the server running java is heavy
About the behavior of ruby Hash # ==
[Java version] The story of serialization
Note on the path of request.getRequestDispatcher
Story of passing Java Gold SE8
The origin of Java lambda expressions
What wasn't fair use in the diversion of Java APIs on Android
How to get the absolute path of a directory running in Java