php / php/php-src

Preloading anonymous class inheriting from autoloaded unlinked class leads to return type error

Open
#10,131 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug Status: Needs Triage
Dominant language
C
Stars
40.4k
Forks
8.1k
Avg merge
2d 13h
Merged PRs (30d)
96

Description

Description
Issue

When using PHP-FPM that is, and only starting when the respective code is used the second time.

The following code:

// preload.php
<?php
require __DIR__ . '/Main.php';

// index.php
<?php
require __DIR__ . '/Main.php';
spl_autoload_register(function ($class) {
    if ($class === 'SomeType') require __DIR__ . '/SomeType.php';
});

var_dump((new Main)->create());

// Main.php
<?php
class Main {
    function create(): SomeType {
        return new class extends SomeType {};
    }
}

// SomeType.php
<?php
class SomeType {}
# php.ini
opcache.enable=1
opcache.optimization_level=-1
opcache.preload=/var/www/html/preload.php
opcache.preload_user=www-data

Where index.php is called twice

Resulted in this output:

# During preload
Warning: Can't preload unlinked class SomeType@anonymous: Unknown parent SomeType in /var/www/html/Main.php on line 4

# Call 1
object(SomeType@anonymous)#3 (0) { }

# Call 2
Fatal error: Uncaught TypeError: Main::create(): Return value must be of type SomeType, SomeType@anonymous returned in /var/www/html/Main.php:4
Stack trace:
#0 /var/www/html/index.php(7): Main->create()
#1 {main}
  thrown in /var/www/html/Main.php on line 4

But I expected this output instead:

# During preload
Warning: Can't preload unlinked class SomeType@anonymous: Unknown parent SomeType in /var/www/html/Main.php on line 4

# Call 1
object(SomeType@anonymous)#3 (0) { }

# Call 2
object(SomeType@anonymous)#3 (0) { }

Reproducing

This can be reproduced by additionally configuring these files

# php-fpm-www.conf
[www]
user=www-data
group=www-data
listen=127.0.0.1:9000
pm=static
pm.max_children=1
# nginx.conf
events {
    worker_connections  4096;
}
http {
    server {
        location ~ \.php$ {
            fastcgi_pass php-test:9000;
            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
            fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name;
            include fastcgi_params;
        }
    }
}

and then running

docker run --rm -it --name php-test -v $(pwd):/var/www/html -v $(pwd)/php-fpm-www.conf:/usr/local/etc/php-fpm.d/www.conf -v $(pwd)/php.ini:/usr/local/etc/php/conf.d/php.ini php:8.1.13-fpm-bullseye bash -c "docker-php-ext-install opcache && php-fpm"
# separately / detached
docker run --rm -it -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf -p 8080:80 --link php-test nginx:1.23.3
curl http://localhost:8080/index.php
curl http://localhost:8080/index.php

Segfaults

Under certain circumstances, this leads to segfaults, but I haven't been able to setup a sharable reproducible case.
Occasionally between segfaults I get errors like these:

  • Uncaught exception: Class "" not found
  • Uncaught exception: Class "H?" not found

Backtrace from such a segfault:

Core was generated by `php-fpm: pool www      '.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00005564d172fe35 in zend_lookup_class_ex (name=0x800000017c9e55d4, key=0x42973350, flags=2560) at /usr/src/php/Zend/zend_execute_API.c:1050
1050    /usr/src/php/Zend/zend_execute_API.c: No such file or directory.
[Current thread is 1 (Thread 0x7f8a9adbe980 (LWP 263))]
(gdb) bt
#0  0x00005564d172fe35 in zend_lookup_class_ex (name=0x800000017c9e55d4, key=0x42973350, flags=2560) at /usr/src/php/Zend/zend_execute_API.c:1050
#1  0x00005564d1731092 in zend_fetch_class_by_name (class_name=0x800000017c9e55d4, key=0x42973350, fetch_type=2560) at /usr/src/php/Zend/zend_execute_API.c:1601
#2  0x00005564d184233d in zend_do_link_class (ce=0x7f8a96404338, lc_parent_name=0x42973350, key=0x42973000) at /usr/src/php/Zend/zend_inheritance.c:2715
#3  0x00005564d178bae1 in ZEND_DECLARE_ANON_CLASS_SPEC_HANDLER () at /usr/src/php/Zend/zend_vm_execute.h:2955
#4  0x00005564d17fcd2c in execute_ex (ex=0x7f8a9aa14020) at /usr/src/php/Zend/zend_vm_execute.h:55863
#5  0x00005564d18014fa in zend_execute (op_array=0x7f8a9aa75140, return_value=0x0) at /usr/src/php/Zend/zend_vm_execute.h:60147
#6  0x00005564d17488dc in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /usr/src/php/Zend/zend.c:1799
#7  0x00005564d169e08b in php_execute_script (primary_file=0x7fff379ad520) at /usr/src/php/main/main.c:2541
#8  0x00005564d18cf2d5 in main (argc=1, argv=0x7fff379ad9a8) at /usr/src/php/sapi/fpm/fpm/fpm_main.c:1914
PHP Version

8.1.13

Operating System

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the PHP-FPM preload case with Main.php, SomeType.php, preload.php, and the supplied Docker configuration, then inspect zend_inheritance.c, zend_execute_API.c, and the anonymous-class handler in zend_vm_execute.h. Trace the second request and add a regression test covering the declared return type, repeated execution, and the reported preload warning without a TypeError or crash.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.