Skip to content

Commit 0a74da3

Browse files
committed
Add support for replaying warnings in opcache
If opcache.record_warnings is enabled, opcache will record compilation warnings and replay them when the file is included again. The primary use case I have in mind for this is automated testing of the opcache file cache. This resolves bug #76535.
1 parent 1b1d313 commit 0a74da3

12 files changed

+174
-0
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ PHP NEWS
9090
- OpCache:
9191
. Fixed bug #78654 (Incorrectly computed opcache checksum on files with
9292
non-ascii characters). (mhagstrand)
93+
. Fixed bug #76535 (Opcache does not replay compile-time warnings). (Nikita)
9394

9495
- PCRE:
9596
. Don't ignore invalid escape sequences. (sjon)

UPGRADING

+5
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,11 @@ PHP 8.0 UPGRADE NOTES
517517
. Introduce DOMParentNode and DOMChildNode with new traversal and manipulation APIs
518518
RFC: https://wiki.php.net/rfc/dom_living_standard_api
519519

520+
- Opcache:
521+
. If the opcache.record_warnings ini setting is enabled, opcache will record
522+
compile-time warnings and replay them on the next include, even if it is
523+
served from cache.
524+
520525
- Zip:
521526
. Extension updated to version 1.19.0
522527
. New ZipArchive::lastId property to get index value of last added entry.

ext/opcache/ZendAccelerator.c

+66
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ zend_bool fallback_process = 0; /* process uses file cache fallback */
121121
static zend_op_array *(*accelerator_orig_compile_file)(zend_file_handle *file_handle, int type);
122122
static int (*accelerator_orig_zend_stream_open_function)(const char *filename, zend_file_handle *handle );
123123
static zend_string *(*accelerator_orig_zend_resolve_path)(const char *filename, size_t filename_len);
124+
static void (*accelerator_orig_zend_error_cb)(int type, const char *error_filename, const uint32_t error_lineno, const char *format, va_list args);
124125
static zif_handler orig_chdir = NULL;
125126
static ZEND_INI_MH((*orig_include_path_on_modify)) = NULL;
126127
static int (*orig_post_startup_cb)(void);
@@ -1661,6 +1662,56 @@ static void zend_accel_init_auto_globals(void)
16611662
}
16621663
}
16631664

1665+
static void persistent_error_cb(int type, const char *error_filename, const uint32_t error_lineno, const char *format, va_list args) {
1666+
if (ZCG(record_warnings)) {
1667+
zend_recorded_warning *warning = emalloc(sizeof(zend_recorded_warning));
1668+
va_list args_copy;
1669+
warning->type = type;
1670+
warning->error_lineno = error_lineno;
1671+
warning->error_filename = zend_string_init(error_filename, strlen(error_filename), 0);
1672+
va_copy(args_copy, args);
1673+
warning->error_message = zend_vstrpprintf(0, format, args_copy);
1674+
va_end(args_copy);
1675+
1676+
ZCG(num_warnings)++;
1677+
ZCG(warnings) = erealloc(ZCG(warnings), sizeof(zend_recorded_warning) * ZCG(num_warnings));
1678+
ZCG(warnings)[ZCG(num_warnings)-1] = warning;
1679+
}
1680+
accelerator_orig_zend_error_cb(type, error_filename, error_lineno, format, args);
1681+
}
1682+
1683+
/* Hack to get us a va_list to pass to zend_error_cb. */
1684+
static void replay_warning_helper(const zend_recorded_warning *warning, ...) {
1685+
va_list va;
1686+
va_start(va, warning);
1687+
accelerator_orig_zend_error_cb(
1688+
warning->type, ZSTR_VAL(warning->error_filename), warning->error_lineno, "%s", va);
1689+
va_end(va);
1690+
}
1691+
1692+
static void replay_warnings(zend_persistent_script *script) {
1693+
for (uint32_t i = 0; i < script->num_warnings; i++) {
1694+
zend_recorded_warning *warning = script->warnings[i];
1695+
replay_warning_helper(warning, ZSTR_VAL(warning->error_message));
1696+
}
1697+
}
1698+
1699+
static void free_recorded_warnings() {
1700+
if (!ZCG(num_warnings)) {
1701+
return;
1702+
}
1703+
1704+
for (uint32_t i = 0; i < ZCG(num_warnings); i++) {
1705+
zend_recorded_warning *warning = ZCG(warnings)[i];
1706+
zend_string_release(warning->error_filename);
1707+
zend_string_release(warning->error_message);
1708+
efree(warning);
1709+
}
1710+
efree(ZCG(warnings));
1711+
ZCG(warnings) = NULL;
1712+
ZCG(num_warnings) = 0;
1713+
}
1714+
16641715
static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handle, int type, const char *key, zend_op_array **op_array_p)
16651716
{
16661717
zend_persistent_script *new_persistent_script;
@@ -1739,6 +1790,9 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl
17391790

17401791
/* Override them with ours */
17411792
ZVAL_UNDEF(&EG(user_error_handler));
1793+
ZCG(record_warnings) = ZCG(accel_directives).record_warnings;
1794+
ZCG(num_warnings) = 0;
1795+
ZCG(warnings) = NULL;
17421796

17431797
zend_try {
17441798
orig_compiler_options = CG(compiler_options);
@@ -1761,9 +1815,11 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl
17611815
/* Restore originals */
17621816
CG(active_op_array) = orig_active_op_array;
17631817
EG(user_error_handler) = orig_user_error_handler;
1818+
ZCG(record_warnings) = 0;
17641819

17651820
if (!op_array) {
17661821
/* compilation failed */
1822+
free_recorded_warnings();
17671823
if (do_bailout) {
17681824
zend_bailout();
17691825
}
@@ -1782,6 +1838,10 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl
17821838
(op_array->fn_flags & ZEND_ACC_EARLY_BINDING) ?
17831839
zend_build_delayed_early_binding_list(op_array) :
17841840
(uint32_t)-1;
1841+
new_persistent_script->num_warnings = ZCG(num_warnings);
1842+
new_persistent_script->warnings = ZCG(warnings);
1843+
ZCG(num_warnings) = 0;
1844+
ZCG(warnings) = NULL;
17851845

17861846
efree(op_array); /* we have valid persistent_script, so it's safe to free op_array */
17871847

@@ -1866,6 +1926,7 @@ zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type)
18661926
}
18671927
}
18681928
}
1929+
replay_warnings(persistent_script);
18691930
zend_file_handle_dtor(file_handle);
18701931

18711932
if (persistent_script->ping_auto_globals_mask) {
@@ -2187,6 +2248,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type)
21872248
}
21882249
}
21892250
}
2251+
replay_warnings(persistent_script);
21902252
zend_file_handle_dtor(file_handle);
21912253
from_shared_memory = 1;
21922254
}
@@ -3051,6 +3113,10 @@ static int accel_post_startup(void)
30513113
accelerator_orig_zend_resolve_path = zend_resolve_path;
30523114
zend_resolve_path = persistent_zend_resolve_path;
30533115

3116+
/* Override error callback, so we can store errors that occur during compilation */
3117+
accelerator_orig_zend_error_cb = zend_error_cb;
3118+
zend_error_cb = persistent_error_cb;
3119+
30543120
/* Override chdir() function */
30553121
if ((func = zend_hash_str_find_ptr(CG(function_table), "chdir", sizeof("chdir")-1)) != NULL &&
30563122
func->type == ZEND_INTERNAL_FUNCTION) {

ext/opcache/ZendAccelerator.h

+14
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ typedef enum _zend_accel_restart_reason {
109109
ACCEL_RESTART_USER /* restart scheduled by opcache_reset() */
110110
} zend_accel_restart_reason;
111111

112+
typedef struct _zend_recorded_warning {
113+
int type;
114+
uint32_t error_lineno;
115+
zend_string *error_filename;
116+
zend_string *error_message;
117+
} zend_recorded_warning;
118+
112119
typedef struct _zend_persistent_script {
113120
zend_script script;
114121
zend_long compiler_halt_offset; /* position of __HALT_COMPILER or -1 */
@@ -117,6 +124,8 @@ typedef struct _zend_persistent_script {
117124
zend_bool corrupted;
118125
zend_bool is_phar;
119126
zend_bool empty;
127+
uint32_t num_warnings;
128+
zend_recorded_warning **warnings;
120129

121130
void *mem; /* shared memory area used by script structures */
122131
size_t size; /* size of used shared memory */
@@ -151,6 +160,7 @@ typedef struct _zend_accel_directives {
151160
zend_bool validate_timestamps;
152161
zend_bool revalidate_path;
153162
zend_bool save_comments;
163+
zend_bool record_warnings;
154164
zend_bool protect_memory;
155165
zend_bool file_override_enabled;
156166
zend_bool enable_cli;
@@ -221,6 +231,10 @@ typedef struct _zend_accel_globals {
221231
void *arena_mem;
222232
zend_persistent_script *current_persistent_script;
223233
zend_bool is_immutable_class;
234+
/* Temporary storage for warnings before they are moved into persistent_script. */
235+
zend_bool record_warnings;
236+
uint32_t num_warnings;
237+
zend_recorded_warning **warnings;
224238
/* cache to save hash lookup on the same INCLUDE opcode */
225239
const zend_op *cache_opline;
226240
zend_persistent_script *cache_persistent_script;

ext/opcache/tests/warning_replay.inc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
declare(unknown=1);

ext/opcache/tests/warning_replay.phpt

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Opcache should replay compilation warnings if opcache.record_warnings=1
3+
--INI--
4+
opcache.record_warnings=1
5+
--FILE--
6+
<?php
7+
8+
require __DIR__ . '/warning_replay.inc';
9+
require __DIR__ . '/warning_replay.inc';
10+
11+
?>
12+
--EXPECTF--
13+
Warning: Unsupported declare 'unknown' in %swarning_replay.inc on line 3
14+
15+
Warning: Unsupported declare 'unknown' in %swarning_replay.inc on line 3

ext/opcache/zend_accelerator_module.c

+2
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ ZEND_INI_BEGIN()
280280

281281
STD_PHP_INI_BOOLEAN("opcache.protect_memory" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.protect_memory, zend_accel_globals, accel_globals)
282282
STD_PHP_INI_BOOLEAN("opcache.save_comments" , "1" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.save_comments, zend_accel_globals, accel_globals)
283+
STD_PHP_INI_BOOLEAN("opcache.record_warnings" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.record_warnings, zend_accel_globals, accel_globals)
283284

284285
STD_PHP_INI_ENTRY("opcache.optimization_level" , DEFAULT_OPTIMIZATION_LEVEL , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.optimization_level, zend_accel_globals, accel_globals)
285286
STD_PHP_INI_ENTRY("opcache.opt_debug_level" , "0" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.opt_debug_level, zend_accel_globals, accel_globals)
@@ -767,6 +768,7 @@ ZEND_FUNCTION(opcache_get_configuration)
767768

768769
add_assoc_bool(&directives, "opcache.protect_memory", ZCG(accel_directives).protect_memory);
769770
add_assoc_bool(&directives, "opcache.save_comments", ZCG(accel_directives).save_comments);
771+
add_assoc_bool(&directives, "opcache.record_warnings", ZCG(accel_directives).record_warnings);
770772
add_assoc_bool(&directives, "opcache.enable_file_override", ZCG(accel_directives).file_override_enabled);
771773
add_assoc_long(&directives, "opcache.optimization_level", ZCG(accel_directives).optimization_level);
772774

ext/opcache/zend_file_cache.c

+34
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,26 @@ static void zend_file_cache_serialize_class(zval *zv,
802802
ZEND_MAP_PTR_INIT(ce->static_members_table, &ce->default_static_members_table);
803803
}
804804

805+
static void zend_file_cache_serialize_warnings(
806+
zend_persistent_script *script, zend_file_cache_metainfo *info, void *buf)
807+
{
808+
if (script->warnings) {
809+
zend_recorded_warning **warnings;
810+
SERIALIZE_PTR(script->warnings);
811+
warnings = script->warnings;
812+
UNSERIALIZE_PTR(warnings);
813+
814+
for (uint32_t i = 0; i < script->num_warnings; i++) {
815+
zend_recorded_warning *warning;
816+
SERIALIZE_PTR(warnings[i]);
817+
warning = warnings[i];
818+
UNSERIALIZE_PTR(warning);
819+
SERIALIZE_STR(warning->error_filename);
820+
SERIALIZE_STR(warning->error_message);
821+
}
822+
}
823+
}
824+
805825
static void zend_file_cache_serialize(zend_persistent_script *script,
806826
zend_file_cache_metainfo *info,
807827
void *buf)
@@ -823,6 +843,7 @@ static void zend_file_cache_serialize(zend_persistent_script *script,
823843
zend_file_cache_serialize_hash(&new_script->script.class_table, script, info, buf, zend_file_cache_serialize_class);
824844
zend_file_cache_serialize_hash(&new_script->script.function_table, script, info, buf, zend_file_cache_serialize_func);
825845
zend_file_cache_serialize_op_array(&new_script->script.main_op_array, script, info, buf);
846+
zend_file_cache_serialize_warnings(new_script, info, buf);
826847

827848
SERIALIZE_PTR(new_script->arena_mem);
828849
new_script->mem = NULL;
@@ -1506,6 +1527,18 @@ static void zend_file_cache_unserialize_class(zval *zv,
15061527
}
15071528
}
15081529

1530+
static void zend_file_cache_unserialize_warnings(zend_persistent_script *script, void *buf)
1531+
{
1532+
if (script->warnings) {
1533+
UNSERIALIZE_PTR(script->warnings);
1534+
for (uint32_t i = 0; i < script->num_warnings; i++) {
1535+
UNSERIALIZE_PTR(script->warnings[i]);
1536+
UNSERIALIZE_STR(script->warnings[i]->error_filename);
1537+
UNSERIALIZE_STR(script->warnings[i]->error_message);
1538+
}
1539+
}
1540+
}
1541+
15091542
static void zend_file_cache_unserialize(zend_persistent_script *script,
15101543
void *buf)
15111544
{
@@ -1518,6 +1551,7 @@ static void zend_file_cache_unserialize(zend_persistent_script *script,
15181551
zend_file_cache_unserialize_hash(&script->script.function_table,
15191552
script, buf, zend_file_cache_unserialize_func, ZEND_FUNCTION_DTOR);
15201553
zend_file_cache_unserialize_op_array(&script->script.main_op_array, script, buf);
1554+
zend_file_cache_unserialize_warnings(script, buf);
15211555

15221556
UNSERIALIZE_PTR(script->arena_mem);
15231557
}

ext/opcache/zend_persist.c

+14
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,19 @@ static void zend_accel_persist_class_table(HashTable *class_table)
10871087
} ZEND_HASH_FOREACH_END();
10881088
}
10891089

1090+
static void zend_persist_warnings(zend_persistent_script *script) {
1091+
if (script->warnings) {
1092+
script->warnings = zend_shared_memdup_free(
1093+
script->warnings, script->num_warnings * sizeof(zend_recorded_warning *));
1094+
for (uint32_t i = 0; i < script->num_warnings; i++) {
1095+
script->warnings[i] = zend_shared_memdup_free(
1096+
script->warnings[i], sizeof(zend_recorded_warning));
1097+
zend_accel_store_string(script->warnings[i]->error_filename);
1098+
zend_accel_store_string(script->warnings[i]->error_message);
1099+
}
1100+
}
1101+
}
1102+
10901103
zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script, const char **key, unsigned int key_length, int for_shm)
10911104
{
10921105
Bucket *p;
@@ -1136,6 +1149,7 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script
11361149
zend_persist_op_array(&p->val);
11371150
} ZEND_HASH_FOREACH_END();
11381151
zend_persist_op_array_ex(&script->script.main_op_array, script);
1152+
zend_persist_warnings(script);
11391153

11401154
if (for_shm) {
11411155
ZCSG(map_ptr_last) = CG(map_ptr_last);

ext/opcache/zend_persist_calc.c

+10
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,15 @@ static void zend_accel_persist_class_table_calc(HashTable *class_table)
514514
} ZEND_HASH_FOREACH_END();
515515
}
516516

517+
static void zend_persist_warnings_calc(zend_persistent_script *script) {
518+
ADD_SIZE(script->num_warnings * sizeof(zend_recorded_warning *));
519+
for (uint32_t i = 0; i < script->num_warnings; i++) {
520+
ADD_SIZE(sizeof(zend_recorded_warning));
521+
ADD_STRING(script->warnings[i]->error_filename);
522+
ADD_STRING(script->warnings[i]->error_message);
523+
}
524+
}
525+
517526
uint32_t zend_accel_script_persist_calc(zend_persistent_script *new_persistent_script, const char *key, unsigned int key_length, int for_shm)
518527
{
519528
Bucket *p;
@@ -556,6 +565,7 @@ uint32_t zend_accel_script_persist_calc(zend_persistent_script *new_persistent_s
556565
zend_persist_op_array_calc(&p->val);
557566
} ZEND_HASH_FOREACH_END();
558567
zend_persist_op_array_calc_ex(&new_persistent_script->script.main_op_array);
568+
zend_persist_warnings_calc(new_persistent_script);
559569

560570
#if defined(__AVX__) || defined(__SSE2__)
561571
/* Align size to 64-byte boundary */

php.ini-development

+5
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,11 @@ ldap.max_links = -1
17761776
; size of the optimized code.
17771777
;opcache.save_comments=1
17781778

1779+
; If enabled, compilation warnings (including notices and deprecations) will
1780+
; be recorded and replayed each time a file is included. Otherwise, compilation
1781+
; warnings will only be emitted when the file is first cached.
1782+
;opcache.record_warnings=0
1783+
17791784
; Allow file existence override (file_exists, etc.) performance feature.
17801785
;opcache.enable_file_override=0
17811786

php.ini-production

+5
Original file line numberDiff line numberDiff line change
@@ -1778,6 +1778,11 @@ ldap.max_links = -1
17781778
; size of the optimized code.
17791779
;opcache.save_comments=1
17801780

1781+
; If enabled, compilation warnings (including notices and deprecations) will
1782+
; be recorded and replayed each time a file is included. Otherwise, compilation
1783+
; warnings will only be emitted when the file is first cached.
1784+
;opcache.record_warnings=0
1785+
17811786
; Allow file existence override (file_exists, etc.) performance feature.
17821787
;opcache.enable_file_override=0
17831788

0 commit comments

Comments
 (0)