php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #77615 Undefined class constant 'self::DOES_EXIST'
Submitted: 2019-02-13 19:44 UTC Modified: 2019-02-18 10:51 UTC
From: mberchtold at gmail dot com Assigned: dmitry (profile)
Status: Closed Package: opcache
PHP Version: 7.4 OS: Windows 10 x64
Private report: No CVE-ID: None
 [2019-02-13 19:44 UTC] mberchtold at gmail dot com
Description:
------------
I'm testing PHP 7.4 (https://windows.php.net/downloads/snaps/ostc/7.4/2019-02-13/) with preloading on Windows 10. The following exception is thrown and PHP crashes.
Undefined class constant 'self::DOES_EXIST'

in zend_constants.c
				if ((flags & ZEND_FETCH_CLASS_SILENT) == 0) {
					zend_throw_error(NULL, "Undefined class constant '%s::%s'", ZSTR_VAL(class_name), ZSTR_VAL(constant_name));
					goto failure;
				}

The only reference to DOES_EXIST can be found in this file:
https://github.com/zendframework/zend-validator/blob/master/src/File/NotExists.php

Stack trace:
 	php7.dll!zend_exception_error(_zend_object * ex, int severity) Line 969	C
 	php7.dll!zend_error_va_list(int type, const char * format, char * args) Line 1283	C
 	php7.dll!zend_error(int type, const char * format, ...) Line 1478	C
 	php7.dll!zend_throw_error(_zend_class_entry * exception_ce, const char * format, ...) Line 1516	C
>	php7.dll!zend_get_constant_ex(_zend_string * cname, _zend_class_entry * scope, unsigned int flags) Line 376	C
 	php7.dll!zend_ast_evaluate(_zval_struct * result, _zend_ast * ast, _zend_class_entry * scope) Line 505	C
 	php7.dll!zend_ast_evaluate(_zval_struct * result, _zend_ast * ast, _zend_class_entry * scope) Line 646	C
 	php7.dll!zval_update_constant_ex(_zval_struct * p, _zend_class_entry * scope) Line 614	C
 	php_opcache.dll!preload_link() Line 3379	C
 	php_opcache.dll!accel_preload(const char * config) Line 3809	C
 	[Inline Frame] php_opcache.dll!accel_finish_startup() Line 3992	C
 	php_opcache.dll!accel_post_startup() Line 2927	C
 	php7.dll!zend_post_startup() Line 991	C
 	php7.dll!php_module_startup(_sapi_module_struct * sf, _zend_module_entry * additional_modules, unsigned int num_additional_modules) Line 2358	C
 	php.exe!php_cli_startup(_sapi_module_struct * sapi_module) Line 415	C
 	php.exe!main(int argc, char * * argv) Line 1351	C
 	[Inline Frame] php.exe!invoke_main() Line 78	C++
 	php.exe!__scrt_common_main_seh() Line 288	C++
 	kernel32.dll!BaseThreadInitThunk()	Unknown
 	ntdll.dll!RtlUserThreadStart()	Unknown


Test script:
---------------
preload zend framework 3 with PHP 7.4 on Windows. Maybe it is enough to preload this file alone:
https://github.com/zendframework/zend-validator/blob/master/src/File/NotExists.php

Expected result:
----------------
No crash.

Actual result:
--------------
Access violation.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2019-02-13 19:54 UTC] mberchtold at gmail dot com
To reproduce it, it is sufficient to preload the zend-validator\src folder from ZF3:
_preload(['C:\vendor\zendframework\zend-validator\src']);
 [2019-02-13 19:56 UTC] mberchtold at gmail dot com
This is the preload.php script I'm using:

<?php
function _preload($preload, string $pattern = "/\.php$/", array $ignore = []) {
  if (is_array($preload)) {
    foreach ($preload as $path) {
      _preload($path, $pattern, $ignore);
    }
  } else if (is_string($preload)) {
    $path = $preload;
    if (!in_array($path, $ignore)) {
      if (is_dir($path)) {
        if ($dh = opendir($path)) {
          while (($file = readdir($dh)) !== false) {
            if ($file !== "." && $file !== "..") {
              _preload($path . DIRECTORY_SEPARATOR . $file, $pattern, $ignore);
            }
          }
          closedir($dh);
        }
      } else if (is_file($path) && preg_match($pattern, $path)) {
      	
      	// HACK: skip classes with possible reserved names
      	// PHP Fatal error:  Cannot use 'Float' as class name as it is reserved in \vendor\zendframework\zend-db\src\Sql\Ddl\Column\Float.php on line 22
      	$basename = basename($path);
      	if ($basename != 'Float.php'
      		&& $basename != 'Int.php'
      		&& $basename != 'Null.php'
      		&& $basename != 'Object.php'
      		)
      	{
        	if (!opcache_compile_file($path)) {
	          trigger_error("Preloading Failed", E_USER_ERROR);
        	}
      	}
      }
    }
  }
}
_preload(['C:\vendor\zendframework\zend-validator\src']);
 [2019-02-13 21:31 UTC] requinix@php.net
-Package: Compile Failure +Package: opcache -PHP Version: Next Minor Version +PHP Version: 7.4
 [2019-02-14 14:45 UTC] dmitry@php.net
"Typed properties" conflict with "preloading" (even if they are not used).
 [2019-02-14 14:54 UTC] dmitry@php.net
-Assigned To: +Assigned To: nikic
 [2019-02-14 14:54 UTC] dmitry@php.net
This is not a Widows related problem.
The problem introduces by "typed properties" that allocate "ce->properties_info_table" at ARENA.

See https://github.com/php/php-src/blob/master/ext/opcache/zend_persist_calc.c#L391

Then assert traps, because we can't keep direct pointer from shared to process memory.

Nikita, can you please take a look.
 [2019-02-15 09:56 UTC] nikic@php.net
-Assigned To: nikic +Assigned To: dmitry
 [2019-02-15 09:56 UTC] nikic@php.net
@dmitry: I believe the properties_info_table issue should be fixed with https://github.com/php/php-src/commit/ffc7e953ea908f05703d573faadc51707936321c.

However, this doesn't resolve the original issue. Something like

class Foo {
    const A = self::DOES_NOT_EXIST;
}

is enough to reproduce a crash. When we call zend_update_constant_ex it may throw an Error, but this of course doesn't work during preloading.
 [2019-02-15 13:50 UTC] nikic@php.net
I've fixed the exception issue in https://github.com/php/php-src/commit/ade9d5e95b879def3ce5b2b0c117ce1258c211ee.

However, there is still some issue for the following case (run under valgrind):

trait T {
    public function test() {
        return 123;
    }
}

class A {
    const C = UNDEF;
    use T;
}

I believe the problem is that T::test() is shared with A, but one of the classes is preloaded while the other isn't. Depending on where we encounter it first, we might treat it as immutable or not. But not sure what exactly is happening here.
 [2019-02-18 10:51 UTC] dmitry@php.net
-Status: Assigned +Status: Closed
 [2019-02-18 10:51 UTC] dmitry@php.net
The last issue should be fixed by https://github.com/php/php-src/commit/3ef9f23fce327ab3d157c9d63c245f8a743e956d

Please, reopen the bug report, in case of related problems.
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Tue Mar 19 21:01:27 2019 UTC