Scala by-name parameter with default null throws NullPointerException -
the following snippet throws nullpointerexception. expected , normal behavior of scala?
object atest extends app { def getx[t <: x](constr: ⇒ t = null.asinstanceof[t]): unit = { constr } getx() } class x generated (decompied) java code snippet:
public final class atest { public static void main(string[] arrstring) { atest$.module$.main(arrstring); } public static void delayedinit(function0<boxedunit> function0) { atest$.module$.delayedinit(function0); } public static string[] args() { return atest$.module$.args(); } public static void scala$app$_setter_$executionstart_$eq(long l) { atest$.module$.scala$app$_setter_$executionstart_$eq(l); } public static long executionstart() { return atest$.module$.executionstart(); } public static void delayedendpoint$test$atest$1() { atest$.module$.delayedendpoint$test$atest$1(); } public static <t extends x> t getx$default$1() { return atest$.module$.getx$default$1(); } public static <t extends x> void getx(function0<t> function0) { atest$.module$.getx(function0); } } public final class atest$ implements app { public static final atest$ module$; private final long executionstart; private string[] scala$app$$_args; private final listbuffer<function0<boxedunit>> scala$app$$initcode; public static { new test.atest$(); } public long executionstart() { return this.executionstart; } public string[] scala$app$$_args() { return this.scala$app$$_args; } public void scala$app$$_args_$eq(string[] x$1) { this.scala$app$$_args = x$1; } public listbuffer<function0<boxedunit>> scala$app$$initcode() { return this.scala$app$$initcode; } public void scala$app$_setter_$executionstart_$eq(long x$1) { this.executionstart = x$1; } public void scala$app$_setter_$scala$app$$initcode_$eq(listbuffer x$1) { this.scala$app$$initcode = x$1; } public string[] args() { return app.class.args((app)this); } public void delayedinit(function0<boxedunit> body) { app.class.delayedinit((app)this, body); } public void main(string[] args) { app.class.main((app)this, (string[])args); } public <t extends x> void getx(function0<t> constr) { constr.apply(); } public <t extends x> t getx$default$1() { return null; } public final void delayedendpoint$test$atest$1() { this.getx((function0<t>)new scala.serializable(){ public static final long serialversionuid = 0; public final nothing. apply() { return (nothing.)atest$.module$.getx$default$1(); } }); } private atest$() { module$ = this; app.class.$init$((app)this); this.delayedinit((function0<boxedunit>)new atest.delayedinit$body(this)); } } public final class atest$.anonfun extends abstractfunction0<nothing.>implements serializable { public final nothing. apply() { return (nothing.)atest$.module$.getx$default$1(); } } and action part:
public <t extends x> void getx(function0<t> constr) { constr.apply(); } public <t extends x> t getx$default$1() { return null; } public final void delayedendpoint$test$atest$1() { this.getx((function0<t>)new scala.serializable(){ public final nothing. apply() { return (nothing.)atest$.module$.getx$default$1(); } }); } that is: call getx passes new anon function0 apply() calls getx$default$1() null. can not see point npe can thrown.
edit: unresolved issue found: https://issues.scala-lang.org/browse/si-8097
edit: expression null.asinstanceof[t] generates default value type t. in case when scala infers resulting type t nothing come runtime expression
null.asinstanceof[nothing] that throws exeption default nothing exception.
but why snippet throws npe @ last line?
object atest extends app { def getx[t](x: t = null.asinstanceof[t]): t = x getx[nothing]() // ok val x = getx() // ok val y = null println("x= "+x) // prints 'x= null' println(s"y= $y") // prints 'y= null' println(s"x= $x") // throws npe !? println("x==null ? "+(x==null)) // prints 'x= null' } and why snippet throws npe (it different previous in implicit param)?
object atest extends app { def getx[t](x: t = null.asinstanceof[t])(implicit s: string = null): t = x getx() // throws npe !? } so situation still vague. , question open.
so have revise answer bit.
what triggers npe clear byte code, not reverse compiled java code. byte code has more features java code, important 1 being, can have 2 methods differ in return type , different things.
so lets first @ stack trace:
at atest$$anonfun$1.apply(test.scala:7) @ atest$.getx(test.scala:5) @ atest$.delayedendpoint$atest$1(test.scala:7) @ atest$delayedinit$body.apply(test.scala:3) @ scala.function0$class.apply$mcv$sp(function0.scala:40) @ scala.runtime.abstractfunction0.apply$mcv$sp(abstractfunction0.scala:12) @ scala.app$$anonfun$main$1.apply(app.scala:76) @ scala.app$$anonfun$main$1.apply(app.scala:76) @ scala.collection.immutable.list.foreach(list.scala:383) @ scala.collection.generic.traversableforwarder$class.foreach(traversableforwarder.scala:35) @ scala.app$class.main(app.scala:76) @ atest$.main(test.scala:3) @ atest.main(test.scala) so method goes wrong atest$$anonfun$1.apply
lets @ that:
public final scala.runtime.nothing$ apply(); code: 0: getstatic #19 // field atest$.module$:latest$; 3: invokevirtual #23 // method atest$.getx$default$1:()lx; 6: checkcast #25 // class scala/runtime/nothing$ 9: areturn public final java.lang.object apply(); code: 0: aload_0 1: invokevirtual #30 // method apply:()lscala/runtime/nothing$; 4: athrow the first thing notice there 2 methods called apply, 1 called (the athrow hint...) well, lets @ method calling it:
public <t extends x> void getx(scala.function0<t>); code: 0: aload_1 1: invokeinterface #62, 1 // interfacemethod scala/function0.apply:()ljava/lang/object; 6: pop 7: return so, calling 1 returns object , has athrow instruction. why give nullpointer exception?
well, method following: places on stack, invokes other apply method (returning nothing$), method returns null returns our default argument. , have null on stack , execute athrow. , athrow throws npe instead if finds null on stack.
so happens here.
the next question is, why happen?
well, lets @ scalac makes of after typechecking:
object atest extends anyref app { def <init>(): atest.type = { atest.super.<init>(); () }; def getx[t <: x](constr: => t = null.asinstanceof[t]): unit = { constr; () }; <synthetic> def getx$default$1[t <: x]: t = null.asinstanceof[t]; atest.this.getx[nothing](atest.this.getx$default$1[nothing]) } and in case without asinstanceof:
object atest extends anyref app { def <init>(): atest.type = { atest.super.<init>(); () }; def getx[t <: x](constr: => t = null): unit = { constr; () }; <synthetic> def getx$default$1[t <: x]: null = null; atest.this.getx[null](atest.this.getx$default$1[nothing]) } well, somehow information, default parameter null lost in first case.
in second case byte code critical method:
public final java.lang.object apply(); code: 0: aload_0 1: invokevirtual #27 // method apply:()lscala/runtime/null$; 4: pop 5: aconst_null 6: areturn so here, compiler knows, argument null, , generates code box null using class null$.
what should happen?
well, not null pointer exception, sure. why compiler generate athrow in first place? because of asinstanceof[t] becomes asinstanceof[nothing] should throw exception if invoked on null.
let's try, happens if in repl:
"".asinstanceof[nothing] java.lang.classcastexception: java.lang.string cannot cast scala.runtime.nothing$ so far good, , this:
null.asinstanceof[nothing] java.lang.nullpointerexception well, maybe should have started this... seems, code generation asinstanceof has bug , throws wrong exception.
why lower bound :> null fixes problem, clear: inferred type no longer nothing null , instanceof fine.
so more interesting problem why type checker fails on complex example have removed now.
complex example
class x object atest extends app { def getx[t<:x](clas: class[t], constr: ⇒ t = null): t ={ val x = constr if (x == null) clas.newinstance() else x } val clas: class[_ <: x] = classof[x] getx(clas) // ooops: type mismatch.. } well, type checker say:
def getx[t <: x](clas: class[t], constr: => t = null): t = { val x: t = constr; if (x.==(null)) clas.newinstance() else x }; <synthetic> def getx$default$2[t <: x]: null = null; private[this] val clas: class[_ <: x] = classof[x]; <stable> <accessor> def clas: class[_ <: x] = atest.this.clas; atest.this.getx[t](<clas: error>, atest.this.getx$default$2) } somehow cant infer type t, should infer null, because there classes reference types. interestingly, compiler not know that. seems directly use definition class java , type parameter there has no lower bound (because java has no type null), lower bound nothing. tells us, how fix it:
val clas: class[_ >: null <: x] = classof[x] getx(clas) this works. can exactly, wanted in first place. have tell compiler, not interested in classes types cannot nulled.
i think still prefer version option though:
def getx[t <: x](clas: class[t], constr: ⇒ option[t] = none): t = { val x = constr x match { case none => clas.newinstance() case some(x) => x } } val clas: class[_ <: x] = classof[x] getx(clas) its clear, why works: none option[nothing], code can handle class[nothing] fine.
Comments
Post a Comment