haxe
haxe copied to clipboard
[JVM] Operand stack underflow
I have latest Haxe build on Windows and Linux, plus Java (openjdk 11 on Linux) and hxJava as well. Following code
@:multiType
abstract Zero<T>(Null<T>) {
public function new();
@:to public static inline function toFloat(v:Float):Float return 0;
}
class Main {
public static function main() {
var z:Zero<Float> = null;
trace(z);
}
}
produces
Error: Unable to initialize main class haxe.root.Main
Caused by: java.lang.VerifyError: Operand stack underflow
Exception Details:
Location:
haxe/root/Main.main()V @1: dstore_0
Reason:
Attempt to pop empty stack.
Current Frame:
bci: @1
flags: { }
locals: { }
stack: { null }
Bytecode:
0000000: 0147 b200 1326 b800 19bb 001b 5912 1d12
0000010: 1f10 1312 20b7 0024 b600 2ab1
What's the -D dump
of Main
here?
[Block:Void]
[Var z(3286):Zero<Float>] [Const:Zero<Float>] null
[Call:Void]
[Field:(v : Dynamic, ?infos : Null<haxe.PosInfos>) -> Void]
[TypeExpr haxe.Log:Class<haxe.Log>]
[FStatic:(v : Dynamic, ?infos : Null<haxe.PosInfos>) -> Void]
haxe.Log
trace:(v : Dynamic, ?infos : Null<haxe.PosInfos>) -> Void
[Local z(3286):Zero<Float>:Zero<Float>]
[ObjectDecl:{ methodName : java.lang.String, lineNumber : Int, fileName : java.lang.String, className : java.lang.String }]
fileName: [Const:java.lang.String] "source/Main.hx"
lineNumber: [Const:Int] 19
className: [Const:java.lang.String] "Main"
methodName: [Const:java.lang.String] "main"
I guess the compiler follows the underlying type of the abstract and concludes that it's Float
via that @:to
function. And then we end up assigning a null
value to a basic type and everything explodes. This code probably isn't quite sound.
On the other hand, I'm not sure why this manifests as a stack underflow. At the very least this should fail differently.
As I remember, @:multiType abstract resolves Null<T> as T assigning 0.0 value in normal case. It seems null is used here causing this to "explode". Here is normal Java output
double z = 0.0;
I think that only happens to work because genjava uses null
as "default value" in cases like this.
By the way, does it work if you change the return type of that @:to
function to Null<Float>
?
No, same happens
Ah, the reason this comes out as a stack underflow is because double values on the JVM take up two stack slots instead of one. With only aconst_null
on the stack it can't pop two stack elements and thus underflows. So there's no mystery in that part.
Anyway, this code should not make it out of the typer like that. It either has to error or inject the @:to
call.
I think using Float
there should be a compiler error. The underlying type promises that it's Null<T>
and thus nullable, but the concrete type is Float
and not nullable.
However, I think it should work with the return type being Null<Float>
. There's probably a different problem here with the Null
being followed away by someone.
BTW, is it possible for @:to and @:from on @:multiType abstract to return specific value, instead of automatically assign 0 for @:to and return argument value at @:from? I checked how Map is coded, there shouldn't be problem because each return value is as it should be.
I guess not, because if @:multiType abstract is used as a member variable inside the class for example, default value (0 or null) will be used depending on platform, unless return value of @:to is used inside init().
What I'm trying to do is to resolve null value as Math.NaN for @:multiType abstract where Null<Float> resolves as Float.