Godot: Indexing arrays evaluations index twice (get and set), causing bug with non-constant index expressions

Created on 27 Feb 2019  路  2Comments  路  Source: godotengine/godot

Godot version:
3.1 Beta 8

OS/device including version:
Windows 10 64bit

Issue description:
I don't really understand what is happening. But using a random number to access an array causes strange things to happen. I haven't noticed this with anything else.

For example in the script below there is an array with a size of 10. Inside the loop a random index is chosen and then 1 is added to it. After adding every number in the array together the expected result is 100, since the loop ran 100 times. This works in the first case with array a because the random number is assigned to a variable first. But this does not work with array b where the random number is used directly. Array b's total will sometimes be below and even above 100.

func _ready():
    randomize()

    # Random number assigned to a variable first.
    # Total will always be 100
    var a : = []
    for i in 10:
        a.append(0)

    for i in 100:
        var r : int = randi() % 10
        a[r] += 1

    var a_total : = 0
    for i in 10:
        a_total += a[i]

    print("total: ", a_total)
    print(a, "\n")

    # Random number not assigned to variable.
    # Total will sometimes be above or below 100
    var b : = []
    for i in 10:
        b.append(0)

    for i in 100:
        # Issue happens here
        b[randi() % 10] += 1

    var b_total : = 0
    for i in 10:
        b_total += b[i]

    print("total: ", b_total)
    print(b)
bug gdscript

Most helpful comment

Would this mean that:

b[randi() % 10] += 1

compiles as bytecode which actually does this:

var tmp = b[randi() % 10]
tmp += 1
b[randi() % 10] = tmp

Which is incorrect, the indexer expression must only be evaluated once, especially in cases where it's not just a variable or constant.

To test this theory, I tried this:

func _ready():
    randomize()

    var a = []
    for i in 10:
        a.append(0)

    for i in 10:
        print("---")
        a[ran()] += 1


func ran():
    print("Exec")
    return randi() % 10

Surprise.

---
Exec
Exec
---
Exec
Exec
---
Exec
Exec
...

This also happens with dictionaries, and all the way from 2.1.5 to 3.1 beta7.

All 2 comments

Would this mean that:

b[randi() % 10] += 1

compiles as bytecode which actually does this:

var tmp = b[randi() % 10]
tmp += 1
b[randi() % 10] = tmp

Which is incorrect, the indexer expression must only be evaluated once, especially in cases where it's not just a variable or constant.

To test this theory, I tried this:

func _ready():
    randomize()

    var a = []
    for i in 10:
        a.append(0)

    for i in 10:
        print("---")
        a[ran()] += 1


func ran():
    print("Exec")
    return randi() % 10

Surprise.

---
Exec
Exec
---
Exec
Exec
---
Exec
Exec
...

This also happens with dictionaries, and all the way from 2.1.5 to 3.1 beta7.

I get the same result without the randi().

func ran():
    print("Exec")
    return 1
Was this page helpful?
0 / 5 - 0 ratings