Skip to content

Corrupted memory in destructor with weak references #13612

Closed
@mvorisek

Description

@mvorisek

Description

In the code below, var_dump(array_keys($analysingMap->valueWithOwnerCountByIndex)) called twice prints different results even if the value is not changed between the calls.

I did my best to create a minimal repro. It seems the issue is present when weak references and destructor is used.

When opcache and/or GC is disabled, the issue is still present. It seems there is some allocation/refcounting issue in destructors.

repro: https://3v4l.org/h7ZER

<?php

class WeakMap2
{
    private array $weakRefs = [];
    private array $values = [];

    public function offsetSet($object, $value) : void
    {
        $id = spl_object_id($object);

        $this->weakRefs[$id] = \WeakReference::create($object);
        $this->values[$id]   = $value;
    }
}

class WeakAnalysingMapRepro
{
    /* private */ public array $valueWithOwnerCountByIndex = [];

    private WeakMap2 $ownerDestructorHandlers;

    public function __construct()
    {
        $this->ownerDestructorHandlers = new WeakMap2();

        $this->addKeyOwner(new \DateTime());
    }

    protected function addKeyOwner(object $owner)
    {
        $handler = new class($this) {
            private \WeakReference $weakAnalysingMap;

            public function __construct(WeakAnalysingMapRepro $analysingMap)
            {
                $this->weakAnalysingMap = \WeakReference::create($analysingMap);
            }

            public function __destruct()
            {
                $analysingMap = $this->weakAnalysingMap->get();

                var_dump(array_keys($analysingMap->valueWithOwnerCountByIndex));
                \Closure::bind(static function () use ($analysingMap) {
                    var_dump(array_keys($analysingMap->valueWithOwnerCountByIndex));
                }, null, WeakAnalysingMapRepro::class)();
            }

            public function addReference($index): void
            {
                $analysingMap = $this->weakAnalysingMap->get();
                $analysingMap->valueWithOwnerCountByIndex[$index] = true;
            }
        };

        $this->ownerDestructorHandlers->offsetSet($owner, $handler);

        $handler->addReference(10);
    }
}

$map = new WeakAnalysingMapRepro();
unset($map);

echo 'DONE';

Resulted in this output:

array(1) {
  [0]=>
  int(10)
}
array(1) {
  [0]=>
  string(12) "analysingMap"
}
DONE

But I expected this output instead:

array(1) {
  [0]=>
  int(10)
}
array(1) {
  [0]=>
  int(10)
}
DONE

PHP Version

any (tested 7.4, 8.1, 8.3, master)

Operating System

any (tested Windows, Linux)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions