JDK-8059443 : Logical NOT operator throws NullPointerException for null Boolean return values
  • Type: Bug
  • Component: core-libs
  • Sub-Component: jdk.nashorn
  • Affected Version: 8u20
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2014-09-29
  • Updated: 2015-06-04
  • Resolved: 2014-11-03
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u40Fixed 9 b39Fixed
Related Reports
Blocks :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
Using the Nashorn Javascript engine, a statement that uses the logical NOT operator to negate the value return by a method defined with a boxed Boolean return type results in a NullPointerException if the returned value is null. In Javascript null is considered a falsey value, so the statement should result in a 'true' value.

!null // returns true

!myJavaObject.getNullBooleanValue() // should return true if return value is defined as Boolean and returns null

If the referenced method is defined to return Object rather than Boolean, the statement returns the expected "true" value. When defined as Boolean return type a NullPointerException is thrown.



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Execute the provided test case


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
!null = true
!checkKittens = true
!profile.getNullValue() = true
!profile.isLikesKittens() = true
ACTUAL -
!null = true
!checkKittens = true
!profile.getNullValue() = true
Exception in thread "main" java.lang.NullPointerException
	at sun.invoke.util.ValueConversions.unboxBoolean(Unknown Source)
	at jdk.nashorn.internal.scripts.Script$\^eval\_.runScript(<eval>:1)
	at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:535)
	at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:209)
	at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:568)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:525)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:521)
	at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:192)
	at javax.script.AbstractScriptEngine.eval(Unknown Source)
	at NashornBooleanTest.eval(NashornBooleanTest.java:22)
	at NashornBooleanTest.main(NashornBooleanTest.java:18)


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.lang.NullPointerException
	at sun.invoke.util.ValueConversions.unboxBoolean(Unknown Source)
	at jdk.nashorn.internal.scripts.Script$\^eval\_.runScript(<eval>:1)
	at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:535)
	at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:209)
	at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:568)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:525)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:521)
	at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:192)
	at javax.script.AbstractScriptEngine.eval(Unknown Source)
	at NashornBooleanTest.eval(NashornBooleanTest.java:22)
	at NashornBooleanTest.main(NashornBooleanTest.java:18)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;


public class NashornBooleanTest {

	public static void main(String[] args) throws ScriptException {
		ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
		Bindings bindings = new SimpleBindings();
		bindings.put("checkKittens", null);
		bindings.put("profile", new Profile());
		eval("!null", engine, bindings);
		eval("!checkKittens", engine, bindings);
		eval("!profile.getNullValue()", engine, bindings);
		eval("!profile.isLikesKittens()", engine, bindings);
	}

	private static void eval(String statement, ScriptEngine engine, Bindings bindings) throws ScriptException {
		System.out.println(statement + " = " + engine.eval(statement, bindings));
	}

	public static class Profile {
		Boolean likesKittens;

		public Boolean isLikesKittens() {
			return likesKittens;
		}

		public Object getNullValue() {
			return null;
		}
	}
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Write statement without using logical not operator (!) or change method return type to Object.


Comments
I have now fixed this using explicitCastArguments on return value conversions when they're JLS Method Invocation Conversion unboxings. Unfortunately, I discovered that there's a regression in explicitCastArguments, filed it as JDK-8060483. I will only be able to push this fix once that is fixed (this issue is being blocked by it).
15-10-2014

As discussed..
14-10-2014

dup() call the problem in the above diff. Fixing it solves the problem. But during code review, Attila observed the following: [15:16:21] Attila Szegedi: @Sundar: I think this bug shows us a deeper issue, one where we move conversions into the signatures of indy invocations (generally not a bad thing to do, as they can be optimized and eliminated). I wonder if I had a POJO with an "Integer getInteger()" that returns null, I can imagine that "pojo.getInteger() << 1" will also break even though "null << 1" can get evaluated in JS [15:18:26] Attila Szegedi: it might mean that we can't use Java's built in asType for Boolean->boolean conversion (or any unboxing conversion) but rather use our own return filters even for these, ones that produce correct JS coerced values for null [15:29:33] Attila Szegedi: *sigh* Unfortunately, I'm right. I created a small class: public class NullProvider { public static Integer getInteger() { return null; } public static Long getLong() { return null; } public static Double getDouble() { return null; } public static Boolean getBoolean() { return null; } } subsequently, all of these fail in script with NPE in sun.invoke.util.ValueConversions.unbox{Int|Double|Boolean}: if (NullProvider.getBoolean()) { print("yay"); } print(NullProvider.getDouble() / 2.5); print(NullProvider.getInteger() << 1); [15:30:45] Attila Szegedi: (both optimistic and non-optimistic) [15:57:00] Attila Szegedi: well��� can be fixed; we already override some built-in Java asType conversions; I guess we need to add these to the list [15:57:31] Attila Szegedi: (FWIW, MethodHandles.explicitCastArguments will substitute 0 for null when going from reference to primitive type, so it's not a terribly foreign concept) Summary: we have a more generic Java return value conversion issue -- not just with NOT operator. So, we need a generic conversion fix.
14-10-2014

Tried to introduce explicit boolean type conversion (so that null value -> false will happen rather than NPE). Made the following changes to CodeGenerator.loadNOT diff -r 9dc87837f70a src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Fri Oct 10 17:59:22 2014 +0530 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Mon Oct 13 17:06:50 2014 +0530 @@ -3626,12 +3626,14 @@ final Expression expr = unaryNode.getExpression(); if(expr instanceof UnaryNode && expr.isTokenType(TokenType.NOT)) { // !!x is idiomatic boolean cast in JavaScript - loadExpressionAsBoolean(((UnaryNode)expr).getExpression()); + loadExpressionUnbounded(((UnaryNode)expr).getExpression()).dup().convert(Type.BOOLEAN); } else { final Label trueLabel = new Label("true"); final Label afterLabel = new Label("after"); - emitBranch(expr, trueLabel, true); + // emitBranch(expr, trueLabel, true); + loadExpressionUnbounded(expr).dup().convert(Type.BOOLEAN); + method.ifne(trueLabel); method.load(true); method._goto(afterLabel); method.label(trueLabel); While that diff fixes the test case (attachment NashornBooleanTest.java), I got "ant test" failures with that change. Needs further investigation.
13-10-2014