Skip to content

Corrupted memory in destructor with weak references #13612

@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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions