hxcpp icon indicating copy to clipboard operation
hxcpp copied to clipboard

Calling a method on a null value class is allowed.

Open mikaib opened this issue 1 year ago • 8 comments

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

mikaib avatar May 24 '24 22:05 mikaib

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 avatar May 24 '24 22:05 thomasjwebb

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

thomasjwebb avatar May 24 '24 22:05 thomasjwebb

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 avatar Jun 10 '24 11:06 barisyild

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");
    }
}

barisyild avatar Jun 10 '24 11:06 barisyild

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 avatar Jun 10 '24 14:06 thomasjwebb

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.

thomasjwebb avatar Jun 10 '24 15:06 thomasjwebb

Eval had this issue and it was considered to be a bug: https://github.com/HaxeFoundation/haxe/issues/10825

tobil4sk avatar May 13 '25 10:05 tobil4sk

Eval had this issue and it was considered to be a bug: HaxeFoundation/haxe#10825

Yeah in that case, it would seem Haxe's philosophy is to have all the targets behave the same, so yeah hxcpp should also do a nullcheck then, then throw an exception like hashlink does.

thomasjwebb avatar May 13 '25 13:05 thomasjwebb