HaxeFoundation / hxcpp

Runtime files for c++ backend for haxe
Other
295 stars 184 forks source link

Calling a method from an array of classes causes "this" to be null #1110

Open mikaib opened 3 months ago

mikaib commented 3 months ago

If you have an array of classes and you try and index a non-existent index. It will allow you to call methods just fine. But if said method tries to access a variable of the class it will crash, because this is null (however it can call other methods of itself)

This is very weird behaviour to me, but it might be intentional.

class CoolClass {
    public var num:Int;

    public function new(i:Int) {
        num = i;
    }

    public function hi() {
        trace(this);
        trace(num);
    }
}

class Main {
    static function main() {
        trace('Begin');
        var myCoolClasses:Array<CoolClass> = [];

        myCoolClasses.push(new CoolClass(1));
        myCoolClasses.push(new CoolClass(2));

        // Index 0 and 1 available, lets call indexes 0, 1 and 2
        myCoolClasses[0].hi();
        myCoolClasses[1].hi();
        myCoolClasses[2].hi();
        trace("End");
    }
}

afbeelding

thomasjwebb commented 3 months ago

I'm not sure what the design philosophy is but it could be that this is the expected behavior and that haxe is designed to defer to the target's typical handling of the situation. I made a minimal example and tried a few targets

class NullTest
{
    public function new() {}

    public function doSomething() {
        trace(this);
    }
}

class Main
{
    public static function main() {
        var arry = new Array<NullTest>();
        arry[0].doSomething();
    }
}

For python I get: AttributeError: 'NoneType' object has no attribute 'doSomething'

For nodejs I get: TypeError: Cannot read properties of undefined (reading 'doSomething')

For hxcpp I get: src/Main.hx:48: null

For hashlink I get a segfault, which is kinda weird: zsh: segmentation fault hl bin/test.hl I would expect it to either have hxcpp's behavior or python & js's behavior

hxcpp is basically behaving the way C++ behaves. This example:

#include <iostream>

class A
{
public:
A() {};
void draw() { std::cerr << (size_t)this << std::endl; };
};

int main()
{
A *a = nullptr;
a->draw();
}

Outputs 0.

thomasjwebb commented 3 months ago

Oh and if I use hashlink's compiled output it's more explicit that it's an uncaught exception so that's more analogous to the behavior in python and js.

Uncaught exception: Null access

barisyild commented 3 months ago

There is no problem because the Array starts without elements.

var integers:Array<Int> = [];
integers.push(10); // Index 0
integers.push(8); // Index 1

var integer1:Int = integers[0];
var integer2:Int = integers[1];
var integer3:Int = integers[2]; // This value is “null” because we have not written anything to the index 2.
trace(integer1, integer2, integer3); // 10, 8, null
barisyild commented 3 months ago

If you have an array of classes and you try and index a non-existent index. It will allow you to call methods just fine. But if said method tries to access a variable of the class it will crash, because this is null (however it can call other methods of itself)

This is very weird behaviour to me, but it might be intentional.

class CoolClass {
    public var num:Int;

    public function new(i:Int) {
        num = i;
    }

    public function hi() {
        trace(this);
        trace(num);
    }
}

class Main {
    static function main() {
        trace('Begin');
        var myCoolClasses:Array<CoolClass> = [];

        myCoolClasses.push(new CoolClass(1));
        myCoolClasses.push(new CoolClass(2));

        // Index 0 and 1 available, lets call indexes 0, 1 and 2
        myCoolClasses[0].hi();
        myCoolClasses[1].hi();
        myCoolClasses[2].hi();
        trace("End");
    }
}

afbeelding

There, problem solved.

Things to keep in mind; Push function gives index value sequentially. The i value in the class is just a variable in the class and has no effect on the index. You can read as many values as you push, if you read more indexes than you push, you will get a null value.

class CoolClass {
    public var num:Int;

    public function new(i:Int) {
        num = i;
    }

    public function hi() {
        trace(this);
        trace(num);
    }
}

class Main {
    static function main() {
        trace('Begin');
        var myCoolClasses:Array<CoolClass> = [];

        myCoolClasses.push(new CoolClass(1)); // Index 0
        myCoolClasses.push(new CoolClass(2)); // Index 1
        myCoolClasses.push(new CoolClass(3)); // Index 2

        myCoolClasses[0].hi();
        myCoolClasses[1].hi();
        myCoolClasses[2].hi();
        trace("End");
    }
}
thomasjwebb commented 3 months ago

There is no problem because the Array starts without elements.

I think the poster's point is not about arrays but about inconsistent behavior when calling functions on uninitialized objects between targets. The array aspect of this is kind of a red herring. See my first comment where I illustrated the same thing by declaring a variable as an instance of a class but assigning null to it.

thomasjwebb commented 3 months ago

Oh I realized I also used arrays to illustrate the issue in my first reply. Forget about arrays. You can illustrate this without any arrays being involved:

class NullTest
{
    public function new() {}

    public function doSomething() {
        trace(this);
    }
}

class Main
{
    public static function main() {
        var n:NullTest = null;
        n.doSomething();
    }
}

exhibits the same behavior. This is just about differences between how targets handle calling methods on null instances of a class potentially causing confusion when debugging.