haxe icon indicating copy to clipboard operation
haxe copied to clipboard

Generated python code initializes static variables in the wrong order

Open acarioni opened this issue 4 years ago • 6 comments

Environment macos monterey 12.1 haxe 4.2.4 utest 1.13.2 haxe-concurrent 3.0.2

The issue can be reproduced by creating a test case like the following one (even if the issue is triggered by the interaction between the libraries utest and haxe-concurrent, I don’t think that it is caused by them as I’m explaining below)

package foo;

import utest.Assert;

class TestCase extends utest.Test {
  static var ex = hx.concurrent.executor.Executor.create();

  function testFoo() {
    Assert.pass();
  }
}

The reported error is

Traceback (most recent call last):
  File "/Users/foo/temp/bin/python/main.py", line 6819, in <module>
    foo_TestCase.ex = hx_concurrent_executor_Executor.create()
  File "/Users/foo/temp/bin/python/main.py", line 745, in create
    return hx_concurrent_executor_ThreadPoolExecutor(maxConcurrent,autostart)
  File "/Users/foo/temp/bin/python/main.py", line 819, in __init__
    super().__init__()
  File "/Users/foo/temp/bin/python/main.py", line 730, in __init__
    super().__init__()
  File "/Users/foo/temp/bin/python/main.py", line 622, in __init__
    haxe_Log.trace((("[" + Std.string(self)) + "] instantiated."),_hx_AnonObject({'fileName': "hx/concurrent/Service.hx", 'lineNumber': 57, 'className': "hx.concurrent.ServiceBase", 'methodName': "new"}))
  File "/Users/foo/temp/bin/python/main.py", line 1978, in trace
    python_Lib.printString((("" + ("null" if str1 is None else str1)) + HxOverrides.stringOrNull(python_Lib.lineEnd)))
AttributeError: type object 'python_Lib' has no attribute 'lineEnd'
Error: Command failed with error 1

The last part of the generated python file is this

foo_TestCase.ex = hx_concurrent_executor_Executor.create()
hx_concurrent_ConcurrentException.INDENT = "  "
hx_concurrent_executor_ScheduleTools.HOUR_IN_MS = 3600000
hx_concurrent_executor_ScheduleTools.DAY_IN_MS = 86400000
hx_concurrent_executor_ScheduleTools.WEEK_IN_MS = 604800000
hx_concurrent_lock_RLock.isSupported = True
python_Lib.lineEnd = ("\r\n" if ((Sys.systemName() == "Windows")) else "\n")
utest_TestHandler.POLLING_TIME = 10
utest_AccessoryName.SETUP_NAME = "setup"
utest_AccessoryName.SETUP_CLASS_NAME = "setupClass"
utest_AccessoryName.TEARDOWN_NAME = "teardown"
utest_AccessoryName.TEARDOWN_CLASS_NAME = "teardownClass"

foo_TestAll.main()
sys_thread__Thread_Thread_Impl_.processEvents()

As you can see the problem is that the executor hx_concurrent_executor_Executor, which internally uses python_Lib.lineEnd for logging, is created before the variable lineEnd is assigned.

acarioni avatar Jan 24 '22 15:01 acarioni

That might be undefined behavior depending on what that Executor.create function does exactly.

Simn avatar Jan 24 '22 16:01 Simn

But Executor.create prints only some logging messages. In general term, I think that if a class A depends on a class B, haxe should emit code where the static fields of B are initialized before the static fields on A.

acarioni avatar Jan 24 '22 16:01 acarioni

Could you link that function or paste its implementation?

Simn avatar Jan 24 '22 16:01 Simn

Sure. Executor.create calls the constructor of hx.concurrent.ServiceBase, that in python is implemented in the following way

def __init__(self):
        self._stateLock = hx_concurrent_lock_RLock()
        self.state = hx_concurrent_ServiceState.STOPPED
        self.id = hx_concurrent_ServiceBase._ids.incrementAndGet()
        haxe_Log.trace((("[" + Std.string(self)) + "] instantiated."),_hx_AnonObject({'fileName': "hx/concurrent/Service.hx", 'lineNumber': 57, 'className': "hx.concurrent.ServiceBase", 'methodName': "new"}))

The implementation of haxe.Log.trace is

@staticmethod
   def trace(v,infos = None):
       _hx_str = haxe_Log.formatOutput(v,infos)
       str1 = Std.string(_hx_str)
       python_Lib.printString((("" + ("null" if str1 is None else str1)) + HxOverrides.stringOrNull(python_Lib.lineEnd)))

and the implementation of lineEnd is

python_Lib.lineEnd = ("\r\n" if ((Sys.systemName() == "Windows")) else "\n")

But as you can see in the fragment of the global python file in the first post, the statement foo_TestCase.ex = hx_concurrent_executor_Executor.create() is called before the statement python_Lib.lineEnd = ("\r\n" if ((Sys.systemName() == "Windows")) else "\n") causing the exception AttributeError: type object 'python_Lib' has no attribute 'lineEnd'.

acarioni avatar Jan 24 '22 16:01 acarioni

Ah yeah, trace is quite special. Not sure what we can do here without some sort of hack. But I agree that this is pretty unfortunate.

Simn avatar Jan 24 '22 16:01 Simn

I think #5592 is a very similar issue (the static field keywords being initialized too late and causing a crash).

A simple workaround is to write getters for those static fields and give them values on the spot if they're null.

gr4vityWall avatar Apr 02 '22 18:04 gr4vityWall

Same error after transpile to Python when using trace. haxe 4.2.5 utest 1.13.2

ilevd avatar Dec 24 '22 16:12 ilevd

@ilevd @acarioni @Simn a temporary workaround is to add a command to your .hxml to include the missing lines in classes after building. Using SQlite/record-macros triggers this error reliably, and using sed to add the static declarations inside the class make the generated Python code work.

--cmd sed -i '/_hx_class_name = "python.Boot"/c\    _hx_class_name = "python.Boot"\n    keywords = set(["and", "del", "from", "not", "with", "as", "elif", "global", "or", "yield", "assert", "else", "if", "pass", "None", "break", "except", "import", "raise", "True", "class", "exec", "in", "return", "False", "continue", "finally", "is", "try", "def", "for", "lambda", "while"])\n    prefixLength = len("_hx_")' main.py

(To anyone reading this, replace main.py with your python target file name)

gr4vityWall avatar Apr 29 '23 11:04 gr4vityWall