HaxeFoundation / haxe

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

Uncaught exception field access on null #10902

Open ilevd opened 1 year ago

ilevd commented 1 year ago

I have Murmur3 hash implementation with such in the begining:

class Murmur3 {
  private static final seed:Int64 = Int64.fromFloat(0);
  private static final C1:Int64 = Int64.fromFloat(0xcc9e2d51);
  private static final C2:Int64 = Int64.fromFloat(0x1b873593);

Unfortunately, when I try to access these fields, they are null and I get an error:

src/lang/Murmur3.hx:110: characters 3-11 : Uncaught exception field access on null
...

I run it with --interp.

I don't know how that can be, and that is really strange, because when I try to make simple example to reproduce the error - it works.

Maybe it happens only in my project, because there are many classes, inheritance etc or somehow related to math operations in cache algorithm, because of some mix with Int and Int64. At least Haxe doesn't provide any message when writing:

private static final C1:Int = 0xcc9e2d51;

But if write:

private static final C1:Int = 3432918353

There is: "Float should be Int".

But even if I replace Int64 with Int with simple values like 0 or 1, I still have such error.

ilevd commented 1 year ago

So, I have success and reproduce it:

Strucrute:

Main.hx
lang/
   EdnReader.hx
   Keyword.hx
   Murmur3.hx
   Symbol.hx
build.hxml

Main.hx

import lang.EdnReader;

class Main {
  static public function main() {}
}

EdnReader.hx

package lang;

class EdnReader {
  static final V:Keyword = Keyword.create();
}

Keyword.hx

package lang;

class Keyword {
  var h:Int;

  public function new(sym:Symbol) {
    h = sym.hasheq() + 0x9e3779b9;
  }

  public static function create():Keyword {
    return new Keyword(Symbol.create());
  }
}

Symbol.hx

package lang;

class Symbol {
  public static function create():Symbol {
    return new Symbol();
  }

  public function new() {}

  public function hasheq():Int {
    return Murmur3.hash("some string");
  }
}

Murmur3.hx

package lang;

final class Murmur3 {
  private static final seed:Int = 0;
  private static final C1:Int = 0xcc9e2d51;
  private static final C2:Int = 0x1b873593;

  public static function hash(input:String):Int {
    // ERROR: Got:
    // Static vars: ,null,null,null
    // instead of values
    trace("Static vars: ", seed, C1, C2);
    return 0;
  }
}

build.hxml

-cp src
-main Main
--each
--interp
Simn commented 1 year ago

Static initialization order is generally undefined. You can see the problem if you check the output of the JavaScript target:

lang_EdnReader.V = lang_Keyword.create();
lang_Murmur3.seed = 0;
lang_Murmur3.C1 = -862048943;
lang_Murmur3.C2 = 461845907;
Main.main();

The compiler makes some effort to find a good order, but it doesn't deeply inspect every expression because this problem is not always solvable at compile-time anyway.

We could perhaps prioritize static inits that definitely don't depend on anything else, like that Murmur3 class here. However, that won't work with your Int64 version because in that case it depends on the In64 run-time support:

lang_EdnReader.V = lang_Keyword.create();
lang_Murmur3.seed = haxe_Int64Helper.fromFloat(0);
lang_Murmur3.C1 = haxe_Int64Helper.fromFloat(-862048943);
lang_Murmur3.C2 = haxe_Int64Helper.fromFloat(461845907);
Main.main();

Hmm, maybe we could use our internal module dependency list here, and only employ the current expression exploration on cycles. I'll have to think about that!

ilevd commented 1 year ago

Thank you. I handle this by making these constants inline, but later I encounter the error like in https://github.com/HaxeFoundation/haxe/issues/10562 and it seems similar, so maybe at least to do such simple static vars inline where possible to reduce the amount of such errors?