views:

114

answers:

2

It took me many hours to narrow down a problem in some code to this reproducible error, which seems to me like a bug in AVM2. Can anyone shed light on why this is occurring or how to fix it?

When the value at index 1 is deleted and a value is subsequently set at index 0, the non-existent (undefined) value at index 1 will now show up in a foreach loop. I have only been able to produce this outcome with index 1 and 0 (not any other n and n-1).

Run this code:

package 
{
import flash.display.Sprite;
public class Main extends Sprite 
{
    public function Main():void 
    {
        var bar : Array = new Array(6);
        out(bar);

        //proper behavior
        trace("bar[1] = 1", bar[1] = 1);
        out(bar);

        //proper behavior
        trace("delete bar[1]", delete bar[1]);
        out(bar);

        //proper behavior
        trace("bar[4] = 4", bar[4] = 4);
        out(bar);

        //for each loop will now iterate over the undefined position at index 1
        trace("bar[0] = 0", bar[0] = 0);
        out(bar);

        trace("bar[3] = 3", bar[3] = 3);
        out(bar);
    }

    private function out(bar:Array):void
    {
        trace(bar);
        for each(var i : * in bar)
        {
            trace(i);
        }
    }
}

}

It will give this output:

,,,,,
bar[1] = 1 1
,1,,,,
1
delete bar[1] true
,,,,,
bar[4] = 4 4
,,,,4,
4
bar[0] = 0 0
0,,,,4,
0
undefined
4
bar[3] = 3 3
0,,,3,4,
0
undefined
4
3

EDIT: See answer for likely cause of the bug. Unable to find a fix. My solution was to code a special case where if index 0 is being set and index 1 does not exist, delete index 1 after setting index 0. Not exactly an elegant solution :\

+1  A: 

It looks like you're running in to a bug that seems to happen when AS3 arrays are split into a dense array part and a "rest" hashtable part.

It's been reported as an "Actionscript Core Language" bug to Adobe here: FP-3477

There's also a couple of blog-post links there to other developers that have run into the (probably) same issue.

Although your example code doesn't re-insert the same object, there might be related issues to how the VM is optimizing low-range literal numbers (often as shared, constant 'objects')

tbone
They do seem related, yes. Seems like the closest we're going to get to an answer.
nexus
A: 

While clearly a bug, I'd say it has more to do with the fact that the array values are in some form of weird state when initialized as var bar : Array = new Array(6);, they should be iterated over to begin with as far as I can see.

I actually never use delete bar[1] to get rid of a value in an array, I always go for bar.splice(1, 1)*, when doing this all unset indices will begin to trace as undefined, so there's clearly something strange going on.

While I suspect you're not really looking for a workaround, I'd just try to live with the potentially iterated empty positions, either handling them as you encounter them or by doing a for (var i:int = 0; i < bar.length; i++) style loop instead.

If you don't need the array-type behaviours, using Object could work as well, it doesn't seem to have this issue.

* this is just by preference, no real reason.

grapefrukt
Unfortunately splice() and a for loop are so much slower that they aren't viable options in our use case.
nexus
a plain for-loop should be faster than a for-each? http://jacksondunstan.com/articles/358
grapefrukt
Not when the array isn't full. A for loop will iterate over every element, a for each will only iterate over the elements that exist.
nexus