HaxeFoundation / haxe

Haxe - The Cross-Platform Toolkit
https://haxe.org
6.14k stars 653 forks source link

[JVM] Operand stack underflow #9264

Open Ilir-Liburn opened 4 years ago

Ilir-Liburn commented 4 years ago

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
Simn commented 4 years ago

What's the -D dump of Main here?

Ilir-Liburn commented 4 years ago
        [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"
Simn commented 4 years ago

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.

Ilir-Liburn commented 4 years ago

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;
Simn commented 4 years ago

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>?

Ilir-Liburn commented 4 years ago

No, same happens

Simn commented 4 years ago

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.

Simn commented 4 years ago

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.

Ilir-Liburn commented 4 years ago

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.