Category
Language · php-src-strict · include / include_once EG(included_files)
Problem
Zend records every successful include/require in EG(included_files). A later include_once/require_once of the same path must skip re-execution and return boolean true.
This compiler only calls Context::markCompileUnitLoaded() on the *_once opcodes. A prior plain include updates get_included_files() via recordIncludedFile() but does not set the once-guard, so the next include_once re-runs the file and returns its return value again.
include_once twice (no intervening plain include) already matches Zend — the once-table itself works.
Probed 2026-08-18 @ 03a5c7b939 — Zend 8.2.32 vs php bin/vm.php / php bin/jit.php.
| Repro |
Zend 8.2.32 |
VM / JIT (2026-08-18) |
file echo "RUN\n"; return 42; then include $f; include_once $f; |
RUN once, then 42 / true |
RUN twice, 42 / 42 |
include_once $g; include_once $g; (control) |
RUN2 once, 7 / true |
same (green) |
php-src reference
- php/php-src
Zend/zend_execute.c — ZEND_INCLUDE_OR_EVAL, zend_hash_add_empty_element(&EG(included_files), …) on all successful includes; once kinds skip when the key exists and return true
PHP implementation target
Repro
./script/docker-exec.sh -- bash -lc 'php test/repro/maintainer_gap_include_once_return.php'
./script/docker-exec.sh -- bash -lc 'php bin/vm.php test/repro/maintainer_gap_include_once_return.php'
./script/docker-exec.sh -- bash -lc 'php bin/jit.php test/repro/maintainer_gap_include_once_return.php'
Snippet if the repro file is not in tree:
<?php
$f = '/tmp/phpc_once_ret.php';
file_put_contents($f, '<?php echo "RUN\n"; return 42;');
echo "include1="; var_export(include $f); echo "\n";
echo "include_once2="; var_export(include_once $f); echo "\n";
Done when
Category
Language· php-src-strict · include / include_once EG(included_files)Problem
Zend records every successful
include/requireinEG(included_files). A laterinclude_once/require_onceof the same path must skip re-execution and return booleantrue.This compiler only calls
Context::markCompileUnitLoaded()on the*_onceopcodes. A prior plainincludeupdatesget_included_files()viarecordIncludedFile()but does not set the once-guard, so the nextinclude_oncere-runs the file and returns itsreturnvalue again.include_oncetwice (no intervening plaininclude) already matches Zend — the once-table itself works.Probed 2026-08-18 @
03a5c7b939— Zend 8.2.32 vsphp bin/vm.php/php bin/jit.php.echo "RUN\n"; return 42;theninclude $f; include_once $f;RUNonce, then42/trueRUNtwice,42/42include_once $g; include_once $g;(control)RUN2once,7/truephp-src reference
Zend/zend_execute.c—ZEND_INCLUDE_OR_EVAL,zend_hash_add_empty_element(&EG(included_files), …)on all successful includes; once kinds skip when the key exists and returntruePHP implementation target
lib/VM.php—TYPE_INCLUDEhandler (~L9965–9975): callmarkCompileUnitLoaded()for plaininclude/requireas well as*_once(or key the once-guard offrecordIncludedFile/includedFiles)lib/VM/Context.php—markCompileUnitLoaded/isCompileUnitLoaded/recordIncludedFileshould share one table matchingEG(included_files)lib/VM/VmInclude.php— keep skip-returntrue; JIT/AOT go through the same helpervar_export(require_once)int vstrueon an already-once path) and Language: require_once and include_once deduplication #120 / Regression: include/require(+_once) expression returns NULL — Zend returns 1 or file return (re-#4483, Zend/zend_execute.c) #21938 (once return NULL)Repro
Snippet if the repro file is not in tree:
Done when
include/require,include_once/require_onceof the same realpath does not re-execute and returnstrue(VM + JIT; AOT if in scope)include_oncetwice (no prior plain include) still matches Zend.phptundertest/compliance/cases/language/