From a5f87e677cf64ccb1d6eb7a3d25411828d761bc7 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Fri, 15 May 2026 11:15:03 +0000 Subject: [PATCH 01/29] [RFC] OPcache Static Cache Implementation https://wiki.php.net/rfc/opcache_static_cache --- .../assign_obj_ref_track_mutation_order.phpt | 85 + Zend/tests/tracked_mutation_hook_safety.phpt | 78 + Zend/zend.c | 9 + Zend/zend.h | 11 + Zend/zend_execute.c | 42 + Zend/zend_execute.h | 59 +- Zend/zend_globals.h | 4 + Zend/zend_object_handlers.c | 10 + Zend/zend_portability.h | 150 + Zend/zend_vm_def.h | 64 +- Zend/zend_vm_execute.h | 1396 ++++++- ext/date/php_date.c | 254 +- ext/date/php_date.h | 6 + ext/opcache/ZendAccelerator.c | 94 + ext/opcache/ZendAccelerator.h | 11 + ext/opcache/config.m4 | 5 + ext/opcache/config.w32 | 5 + ext/opcache/jit/zend_jit_helpers.c | 9 + ext/opcache/jit/zend_jit_ir.c | 19 +- ext/opcache/opcache.stub.php | 94 + ext/opcache/opcache_arginfo.h | 213 +- ext/opcache/tests/fpm/skipif.inc | 13 + ...s_blob_dynamic_method_statics_jit_001.phpt | 121 + ...ent_static_class_blob_persistence_001.phpt | 75 + ..._static_complex_value_persistence_001.phpt | 86 + ...static_object_assignment_snapshot_001.phpt | 67 + ...fpm_persistent_static_persistence_001.phpt | 80 + ...ent_static_property_array_publish_001.phpt | 63 + ...pm_persistent_static_scope_writes_001.phpt | 122 + ...e_cache_complex_value_persistence_001.phpt | 135 + ..._volatile_cache_direct_cache_safe_001.phpt | 194 + ...e_fpm_volatile_cache_lookup_cache_001.phpt | 91 + ...ested_shared_object_copy_on_write_001.phpt | 110 + ...he_fpm_volatile_cache_persistence_001.phpt | 103 + ...cache_shared_object_copy_on_write_001.phpt | 98 + ...e_persistent_cache_shared_workers_001.phpt | 133 + ...ing_clear_invalidate_after_access_001.phpt | 313 ++ ...c_tracking_reference_object_graph_001.phpt | 252 ++ ...ic_tracking_shared_reference_cell_001.phpt | 323 ++ ext/opcache/tests/fpm/tester.inc | 4 + ...licit_cache_store_delete_zts_threads_001.c | 285 ++ .../persistent_static_zts_threads_001.c | 304 ++ .../persistent_static_zts_threads_001.inc | 100 + .../volatile_cache_process_lock_mode_001.c | 300 ++ ...tile_cache_retired_shared_graph_free_001.c | 304 ++ ..._cache_zts_request_shared_graph_refs_001.c | 476 +++ .../helpers/volatile_cache_zts_threads_001.c | 281 ++ .../helpers/volatile_cache_zts_threads_002.c | 312 ++ ...zts_threads_002_materialization_nested.inc | 58 + ..._zts_threads_002_materialization_plain.inc | 44 + ...tile_cache_zts_threads_002_safe_direct.inc | 110 + .../helpers/volatile_cache_zts_threads_003.c | 373 ++ .../volatile_cache_zts_threads_003_clear.inc | 51 + .../volatile_cache_zts_threads_003_delete.inc | 51 + .../volatile_cache_zts_threads_003_hit.inc | 51 + ...che_zts_threads_003_materialized_clear.inc | 79 + .../volatile_cache_zts_threads_003_miss.inc | 52 + ..._zts_threads_003_volatile_static_class.inc | 87 + ...ute_publish_userland_outside_lock_001.phpt | 63 + ...static_cache_attribute_signatures_001.phpt | 50 + .../static_cache_benchmark_scenarios_001.phpt | 23 + ...abled_without_static_cache_memory_001.phpt | 24 + ...ache_explicit_cache_complex_store_001.phpt | 104 + ..._destructive_mutators_no_deadlock_001.phpt | 233 ++ ..._cache_explicit_cache_fetch_array_001.phpt | 115 + ...cache_fetch_userland_outside_lock_001.phpt | 62 + ...it_cache_fragmentation_relocation_001.phpt | 93 + ...che_fragmentation_relocation_skip_001.phpt | 107 + ...che_explicit_cache_key_validation_001.phpt | 80 + ...e_explicit_cache_lock_clear_reset_001.phpt | 73 + ...ic_cache_explicit_cache_lock_fork_001.phpt | 202 + ...ck_parent_survives_child_shutdown_001.phpt | 96 + ...t_cache_lock_public_mutators_fork_001.phpt | 291 ++ ...che_explicit_cache_lock_rshutdown_001.phpt | 120 + ...c_cache_explicit_cache_lock_store_001.phpt | 77 + ...licit_cache_lock_stripe_collision_001.phpt | 129 + ...cache_prepare_nested_object_reuse_001.phpt | 79 + ...ache_prepare_safe_direct_mutation_001.phpt | 52 + ...licit_cache_prepare_scratch_reuse_001.phpt | 84 + ...t_cache_request_local_object_copy_001.phpt | 145 + ...he_request_local_safe_direct_slot_001.phpt | 140 + ...explicit_cache_request_local_slot_001.phpt | 130 + ...explicit_cache_shared_graph_array_001.phpt | 66 + ...c_cache_explicit_cache_signatures_001.phpt | 68 + ...cit_cache_store_array_invalid_key_001.phpt | 39 + ...cit_cache_store_delete_fork_reuse_001.phpt | 109 + ..._cache_store_delete_request_reuse_001.phpt | 122 + ...it_cache_store_delete_zts_threads_001.phpt | 246 ++ ...static_cache_persistent_cache_api_001.phpt | 55 + ...ent_cache_array_mutation_overflow_001.phpt | 31 + ...ent_cache_atomic_increment_create_001.phpt | 49 + ...rsistent_cache_atomic_non_integer_001.phpt | 31 + ...che_persistent_cache_batch_atomic_001.phpt | 62 + ...atic_cache_persistent_cache_clear_001.phpt | 58 + ...e_persistent_cache_clear_combined_001.phpt | 110 + ...tatic_cache_persistent_cache_info_001.phpt | 60 + ...stent_cache_lock_atomic_increment_001.phpt | 79 + ...c_cache_persistent_cache_overflow_001.phpt | 30 + ...e_persistent_cache_store_overflow_001.phpt | 29 + ...t_static_array_mutation_fast_path_001.phpt | 111 + ...sistent_static_array_mutation_jit_001.phpt | 121 + ...static_attribute_scope_separation_001.phpt | 104 + ...he_persistent_static_capture_miss_001.phpt | 122 + ...ersistent_static_capture_tracking_001.phpt | 240 ++ ...s_blob_dynamic_method_statics_jit_001.phpt | 103 + ...t_static_class_blob_readonly_skip_001.phpt | 94 + ...tent_static_class_blob_single_key_001.phpt | 61 + ...static_class_property_persistence_001.phpt | 51 + ..._static_complex_value_persistence_001.phpt | 71 + ...d_without_persistent_cache_memory_001.phpt | 40 + ...stent_static_inherited_attributes_001.phpt | 62 + ...ache_persistent_static_invalidate_001.phpt | 102 + ...nt_static_invalidate_after_access_001.phpt | 60 + ...local_copy_array_mutation_publish_001.phpt | 128 + ..._static_method_static_persistence_001.phpt | 72 + ...tatic_object_assignment_fast_path_001.phpt | 113 + ...static_object_assignment_snapshot_001.phpt | 40 + ...istent_static_object_dim_mutation_001.phpt | 110 + ...t_static_object_property_mutation_001.phpt | 171 + ...c_property_array_mutation_publish_001.phpt | 71 + ...ersistent_static_readonly_publish_001.phpt | 175 + ...c_recursive_shared_graph_snapshot_001.phpt | 76 + ...tic_cache_persistent_static_reset_001.phpt | 83 + ...sistent_static_reset_after_access_001.phpt | 60 + ...nt_static_store_failure_exception_001.phpt | 117 + ...stent_static_timestamp_invalidate_001.phpt | 53 + ...che_persistent_static_zts_threads_001.phpt | 302 ++ .../tests/static_cache_preload_001.inc | 25 + .../tests/static_cache_preload_001.phpt | 32 + .../static_cache_startup_failure_001.phpt | 63 + ...ic_cache_volatile_cache_allocator_001.phpt | 39 + ...ic_cache_volatile_cache_allocator_002.phpt | 33 + ...static_cache_volatile_cache_basic_001.phpt | 48 + ...atile_cache_combined_delete_clear_001.phpt | 111 + ...atic_cache_volatile_cache_complex_001.phpt | 115 + ...tive_mutators_retire_shared_graph_001.phpt | 139 + ..._volatile_cache_direct_cache_safe_001.phpt | 191 + ..._volatile_cache_direct_cache_safe_002.phpt | 111 + ..._volatile_cache_direct_cache_safe_003.phpt | 203 + ..._volatile_cache_direct_cache_safe_004.phpt | 101 + ...tile_cache_direct_cache_safe_fork_001.phpt | 147 + ...ache_direct_cache_safe_unstorable_001.phpt | 55 + ...cache_direct_cache_safe_visibility_001.inc | 6 + ...ache_direct_cache_safe_visibility_001.phpt | 37 + ...e_cache_expunge_before_relocation_001.phpt | 48 + ...cache_volatile_cache_fetch_exists_001.phpt | 40 + ...cache_volatile_cache_info_surface_001.phpt | 40 + ...cache_volatile_cache_lookup_cache_001.phpt | 37 + ..._volatile_cache_lookup_cache_fork_001.phpt | 117 + ..._cache_materialization_clear_fork_001.phpt | 105 + ..._materialization_clear_reuse_fork_001.phpt | 112 + ...latile_cache_materialization_fork_001.phpt | 101 + ...latile_cache_materialization_fork_002.phpt | 115 + ...e_materialization_overwrite_reuse_001.phpt | 74 + ...ic_cache_volatile_cache_no_atomic_001.phpt | 22 + ...tic_cache_volatile_cache_overflow_001.phpt | 40 + ..._volatile_cache_process_lock_mode_001.phpt | 244 ++ ...e_cache_retired_shared_graph_free_001.phpt | 240 ++ ..._cache_shared_graph_dynamic_array_001.phpt | 62 + ...he_volatile_cache_storable_values_001.phpt | 155 + ...ache_tracked_reference_assignment_001.phpt | 81 + .../static_cache_volatile_cache_ttl_001.phpt | 28 + ..._cache_volatile_cache_ttl_invalid_001.phpt | 35 + ...ic_cache_volatile_cache_ttl_large_001.phpt | 20 + ...che_zts_request_shared_graph_refs_001.phpt | 305 ++ ..._cache_volatile_cache_zts_threads_001.phpt | 289 ++ ..._cache_volatile_cache_zts_threads_002.phpt | 310 ++ ..._cache_volatile_cache_zts_threads_003.phpt | 316 ++ ...olatile_static_clear_after_access_001.phpt | 61 + ...r_isolated_from_persistent_static_001.phpt | 71 + ...ic_cache_volatile_static_strategy_001.phpt | 327 ++ ...static_tracking_alias_value_kinds_001.phpt | 185 + ...ing_clear_invalidate_after_access_001.phpt | 291 ++ ...c_tracking_reference_object_graph_001.phpt | 230 + ...static_tracking_shared_dependency_001.phpt | 260 ++ ...ic_tracking_shared_reference_cell_001.phpt | 301 ++ .../static_cache_volatile_static_ttl_001.phpt | 123 + ...tile_static_ttl_invalid_attribute_001.phpt | 19 + ...cache_volatile_static_ttl_refresh_001.phpt | 68 + ext/opcache/zend_accelerator_module.c | 150 +- ext/opcache/zend_accelerator_module.h | 2 + ext/opcache/zend_opcache_serializer.h | 2044 +++++++++ ext/opcache/zend_static_cache.c | 1757 ++++++++ ext/opcache/zend_static_cache.h | 90 + ext/opcache/zend_static_cache_entries.c | 2038 +++++++++ ext/opcache/zend_static_cache_internal.h | 1083 +++++ ext/opcache/zend_static_cache_shared_graph.c | 1583 +++++++ ext/opcache/zend_static_cache_statics.c | 3717 +++++++++++++++++ ext/opcache/zend_static_cache_storage.c | 1565 +++++++ ext/spl/spl_array.c | 171 +- ext/spl/spl_array.h | 6 + ext/spl/spl_fixedarray.c | 172 +- ext/spl/spl_fixedarray.h | 8 + sapi/fpm/tests/tester.inc | 5 +- 194 files changed, 35592 insertions(+), 175 deletions(-) create mode 100644 Zend/tests/assign_obj_ref_track_mutation_order.phpt create mode 100644 Zend/tests/tracked_mutation_hook_safety.phpt create mode 100644 ext/opcache/tests/fpm/skipif.inc create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_persistent_static_complex_value_persistence_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_persistent_static_persistence_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_persistent_static_scope_writes_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_complex_value_persistence_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_direct_cache_safe_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_lookup_cache_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_nested_shared_object_copy_on_write_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_persistence_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_shared_object_copy_on_write_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_persistent_cache_shared_workers_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_clear_invalidate_after_access_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_reference_object_graph_001.phpt create mode 100644 ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_shared_reference_cell_001.phpt create mode 100644 ext/opcache/tests/fpm/tester.inc create mode 100644 ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c create mode 100644 ext/opcache/tests/helpers/persistent_static_zts_threads_001.c create mode 100644 ext/opcache/tests/helpers/persistent_static_zts_threads_001.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_process_lock_mode_001.c create mode 100644 ext/opcache/tests/helpers/volatile_cache_retired_shared_graph_free_001.c create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_001.c create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_002.c create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_002_materialization_nested.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_002_materialization_plain.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_002_safe_direct.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_003_clear.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_003_delete.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_003_hit.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_003_materialized_clear.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_003_miss.inc create mode 100644 ext/opcache/tests/helpers/volatile_cache_zts_threads_003_volatile_static_class.inc create mode 100644 ext/opcache/tests/static_cache_attribute_publish_userland_outside_lock_001.phpt create mode 100644 ext/opcache/tests/static_cache_attribute_signatures_001.phpt create mode 100644 ext/opcache/tests/static_cache_benchmark_scenarios_001.phpt create mode 100644 ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_complex_store_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_destructive_mutators_no_deadlock_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_fetch_array_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_fetch_userland_outside_lock_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_lock_clear_reset_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_lock_fork_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_lock_parent_survives_child_shutdown_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_lock_public_mutators_fork_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_lock_rshutdown_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_lock_store_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_lock_stripe_collision_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_prepare_nested_object_reuse_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_prepare_safe_direct_mutation_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_prepare_scratch_reuse_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_request_local_object_copy_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_request_local_slot_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_shared_graph_array_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_store_delete_fork_reuse_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_store_delete_request_reuse_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_store_delete_zts_threads_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_api_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_array_mutation_overflow_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_atomic_non_integer_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_batch_atomic_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_clear_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_clear_combined_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_info_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_lock_atomic_increment_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_overflow_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_array_mutation_fast_path_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_array_mutation_jit_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_capture_tracking_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_class_blob_readonly_skip_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_class_property_persistence_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_complex_value_persistence_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_disabled_without_persistent_cache_memory_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_inherited_attributes_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_invalidate_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_invalidate_after_access_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_local_copy_array_mutation_publish_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_method_static_persistence_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_object_assignment_fast_path_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_object_dim_mutation_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_object_property_mutation_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_readonly_publish_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_recursive_shared_graph_snapshot_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_reset_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_reset_after_access_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_timestamp_invalidate_001.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt create mode 100644 ext/opcache/tests/static_cache_preload_001.inc create mode 100644 ext/opcache/tests/static_cache_preload_001.phpt create mode 100644 ext/opcache/tests/static_cache_startup_failure_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_allocator_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_allocator_002.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_basic_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_combined_delete_clear_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_complex_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_destructive_mutators_retire_shared_graph_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_002.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_003.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_004.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_fork_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc create mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_expunge_before_relocation_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_fetch_exists_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_info_surface_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_lookup_cache_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_lookup_cache_fork_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_materialization_clear_fork_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_materialization_clear_reuse_fork_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_materialization_fork_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_materialization_fork_002.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_materialization_overwrite_reuse_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_no_atomic_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_overflow_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_process_lock_mode_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_retired_shared_graph_free_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_shared_graph_dynamic_array_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_tracked_reference_assignment_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_ttl_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_ttl_invalid_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_ttl_large_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_zts_request_shared_graph_refs_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_zts_threads_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_zts_threads_003.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_clear_after_access_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_clear_isolated_from_persistent_static_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_tracking_alias_value_kinds_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_tracking_clear_invalidate_after_access_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_tracking_reference_object_graph_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_tracking_shared_dependency_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_tracking_shared_reference_cell_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_ttl_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_ttl_invalid_attribute_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_ttl_refresh_001.phpt create mode 100644 ext/opcache/zend_opcache_serializer.h create mode 100644 ext/opcache/zend_static_cache.c create mode 100644 ext/opcache/zend_static_cache.h create mode 100644 ext/opcache/zend_static_cache_entries.c create mode 100644 ext/opcache/zend_static_cache_internal.h create mode 100644 ext/opcache/zend_static_cache_shared_graph.c create mode 100644 ext/opcache/zend_static_cache_statics.c create mode 100644 ext/opcache/zend_static_cache_storage.c diff --git a/Zend/tests/assign_obj_ref_track_mutation_order.phpt b/Zend/tests/assign_obj_ref_track_mutation_order.phpt new file mode 100644 index 000000000000..a36db4c09dca --- /dev/null +++ b/Zend/tests/assign_obj_ref_track_mutation_order.phpt @@ -0,0 +1,85 @@ +--TEST-- +ASSIGN_OBJ_REF tracks object mutations before freeing operands +--SKIPIF-- + +--FILE-- +op1.var));', + 'zval_ptr_dtor_nogc(EX_VAR(opline->op2.var));', + 'zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var));', + ]; + $freePositions = []; + + foreach ($freeNeedles as $needle) { + $pos = strpos($section, $needle); + if ($pos !== false) { + $freePositions[] = $pos; + } + } + + if ($trackPos === false) { + throw new RuntimeException($label . ' is missing the expected ASSIGN_OBJ_REF sequence'); + } + + if ($freePositions === []) { + return false; + } + + if ($trackPos > min($freePositions)) { + throw new RuntimeException($label . ' tracks object mutation after freeing operands'); + } + + return true; +} + +$zendDir = dirname(__DIR__); + +$vmDef = file_get_contents($zendDir . '/zend_vm_def.h'); +$defStart = strpos($vmDef, 'ZEND_VM_HANDLER(32, ZEND_ASSIGN_OBJ_REF'); +$defEnd = strpos($vmDef, 'ZEND_VM_HANDLER(33, ZEND_ASSIGN_STATIC_PROP_REF'); +if ($defStart === false || $defEnd === false) { + throw new RuntimeException('Unable to locate ZEND_ASSIGN_OBJ_REF in zend_vm_def.h'); +} + +if (!assertAssignObjRefTrackingOrder(substr($vmDef, $defStart, $defEnd - $defStart), 'zend_vm_def.h')) { + throw new RuntimeException('zend_vm_def.h ASSIGN_OBJ_REF unexpectedly has no operand frees'); +} + +$vmExecute = file_get_contents($zendDir . '/zend_vm_execute.h'); +$sections = preg_split('/(?=static ZEND_OPCODE_HANDLER_RET )/', $vmExecute); +$checked = 0; + +foreach ($sections as $section) { + $headerEnd = strpos($section, "\n"); + $label = $headerEnd === false ? 'zend_vm_execute.h ASSIGN_OBJ_REF handler' : trim(substr($section, 0, $headerEnd)); + if (!preg_match('/\bZEND_ASSIGN_OBJ_REF_[A-Z0-9_]+_HANDLER\b/', $label)) { + continue; + } + + if (assertAssignObjRefTrackingOrder($section, $label)) { + $checked++; + } +} + +if ($checked === 0) { + throw new RuntimeException('Unable to locate generated ZEND_ASSIGN_OBJ_REF handlers in zend_vm_execute.h'); +} + +echo "OK\n"; + +?> +--EXPECT-- +OK diff --git a/Zend/tests/tracked_mutation_hook_safety.phpt b/Zend/tests/tracked_mutation_hook_safety.phpt new file mode 100644 index 000000000000..079cb7d538dc --- /dev/null +++ b/Zend/tests/tracked_mutation_hook_safety.phpt @@ -0,0 +1,78 @@ +--TEST-- +Tracked mutation hooks are called through defensive helpers +--SKIPIF-- + +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/zend.c b/Zend/zend.c index f16b1a30dbbc..964e1e79296c 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -96,6 +96,13 @@ ZEND_API zend_string *(*zend_resolve_path)(zend_string *filename); ZEND_API zend_result (*zend_post_startup_cb)(void) = NULL; ZEND_API void (*zend_post_shutdown_cb)(void) = NULL; ZEND_API void (*zend_accel_schedule_restart_hook)(int reason) = NULL; +ZEND_API void (*zend_class_init_statics_hook)(zend_class_entry *ce) = NULL; +ZEND_API void (*zend_function_init_statics_hook)(zend_execute_data *execute_data) = NULL; +ZEND_API void (*zend_class_static_access_hook)(zend_class_entry *ce) = NULL; +ZEND_API void (*zend_class_static_update_hook)(zend_class_entry *ce) = NULL; +ZEND_API void (*zend_tracked_reference_update_hook)(zend_reference *ref) = NULL; +ZEND_API bool (*zend_tracked_hash_mutation_hook)(HashTable *ht, bool publish) = NULL; +ZEND_API void (*zend_tracked_object_mutation_hook)(zend_object *obj) = NULL; ZEND_ATTRIBUTE_NONNULL ZEND_API zend_result (*zend_random_bytes)(void *bytes, size_t size, char *errstr, size_t errstr_size) = NULL; ZEND_ATTRIBUTE_NONNULL ZEND_API void (*zend_random_bytes_insecure)(zend_random_bytes_insecure_state *state, void *bytes, size_t size) = NULL; @@ -820,6 +827,8 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ #endif executor_globals->saved_fpu_cw_ptr = NULL; executor_globals->active = false; + executor_globals->static_cache_class_access_active = false; + executor_globals->tracked_mutation_hooks_active = false; executor_globals->bailout = NULL; executor_globals->error_handling = EH_NORMAL; executor_globals->exception_class = NULL; diff --git a/Zend/zend.h b/Zend/zend.h index 0d5303192b57..e2cd40c72fd9 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -386,6 +386,17 @@ extern ZEND_API void (*zend_post_shutdown_cb)(void); extern ZEND_API void (*zend_accel_schedule_restart_hook)(int reason); +/* These hooks are used by OPcache Static Cache to restore, publish, and track + * selected VolatileStatic and PersistentStatic state across requests. They remain + * NULL when the static-cache subsystem is not active. */ +extern ZEND_API void (*zend_class_init_statics_hook)(zend_class_entry *ce); +extern ZEND_API void (*zend_function_init_statics_hook)(zend_execute_data *execute_data); +extern ZEND_API void (*zend_class_static_access_hook)(zend_class_entry *ce); +extern ZEND_API void (*zend_class_static_update_hook)(zend_class_entry *ce); +extern ZEND_API void (*zend_tracked_reference_update_hook)(zend_reference *ref); +extern ZEND_API bool (*zend_tracked_hash_mutation_hook)(HashTable *ht, bool publish); +extern ZEND_API void (*zend_tracked_object_mutation_hook)(zend_object *obj); + ZEND_API ZEND_COLD void zend_error(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3); ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3); ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn_unchecked(int type, const char *format, ...); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 4253037fda52..bd06a048214e 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -2908,6 +2908,11 @@ static zend_always_inline void zend_fetch_dimension_address(zval *result, zval * if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { try_array: + if (UNEXPECTED((type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) && + EG(tracked_mutation_hooks_active)) + ) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); fetch_from_array: if (dim == NULL) { @@ -3507,6 +3512,20 @@ static zend_never_inline bool zend_handle_fetch_obj_flags( return 1; } +static zend_always_inline void zend_track_property_array_indirect_mutation(zval *ptr, int type) +{ + zval *tracked = ptr; + + if (UNEXPECTED((type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) && + EG(tracked_mutation_hooks_active)) + ) { + ZVAL_DEREF(tracked); + if (Z_TYPE_P(tracked) == IS_ARRAY) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(tracked), false); + } + } +} + static zend_always_inline void zend_fetch_property_address( zval *result, const zval *container, @@ -3591,6 +3610,7 @@ static zend_always_inline void zend_fetch_property_address( zend_handle_fetch_obj_flags(result, ptr, NULL, prop_info, flags); } } + zend_track_property_array_indirect_mutation(ptr, type); return; } } else if (UNEXPECTED(IS_HOOKED_PROPERTY_OFFSET(prop_offset))) { @@ -3606,6 +3626,7 @@ static zend_always_inline void zend_fetch_property_address( ptr = zend_hash_find_known_hash(zobj->properties, Z_STR_P(prop_ptr)); if (EXPECTED(ptr)) { ZVAL_INDIRECT(result, ptr); + zend_track_property_array_indirect_mutation(ptr, type); return; } } @@ -3653,6 +3674,7 @@ static zend_always_inline void zend_fetch_property_address( } } } + zend_track_property_array_indirect_mutation(ptr, type); end: if (prop_info_p) { @@ -3828,6 +3850,12 @@ static zend_always_inline zval* zend_fetch_static_property_address(zend_property result = CACHED_PTR(cache_slot + sizeof(void *)); property_info = CACHED_PTR(cache_slot + sizeof(void *) * 2); + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_class_static_access_hook != NULL) + ) { + zend_class_static_access_hook(property_info->ce); + } + if ((fetch_type == BP_VAR_R || fetch_type == BP_VAR_RW) && UNEXPECTED(Z_TYPE_P(result) == IS_UNDEF) && ZEND_TYPE_IS_SET(property_info->type)) { @@ -4102,6 +4130,20 @@ ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *orig_value, ui return result; } +zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_track_object_mutation_slow(zend_object *zobj) +{ + if (zobj != NULL && zend_tracked_object_mutation_hook != NULL && EG(exception) == NULL) { + zend_tracked_object_mutation_hook(zobj); + } +} + +zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_track_object_mutation_with_value_slow(zend_object *zobj, zval *value) +{ + if (zobj != NULL && zend_tracked_object_mutation_hook != NULL && EG(exception) == NULL && value != &EG(error_zval)) { + zend_tracked_object_mutation_hook(zobj); + } +} + ZEND_API bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref_ex(const zend_property_info *prop_info, zval *orig_val, bool strict, zend_verify_prop_assignable_by_ref_context context) { zval *val = orig_val; if (Z_ISREF_P(val) && ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(val))) { diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index ba48b19bcfe1..cbbb9ba60a18 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -127,13 +127,36 @@ ZEND_API bool zend_verify_internal_return_type(const zend_function *zf, zval *re ? ZEND_PROPERTY_INFO_SOURCE_TO_LIST((ref)->sources.list)->ptr[0] \ : (ref)->sources.ptr) - ZEND_API void ZEND_FASTCALL zend_ref_add_type_source(zend_property_info_source_list *source_list, zend_property_info *prop); ZEND_API void ZEND_FASTCALL zend_ref_del_type_source(zend_property_info_source_list *source_list, const zend_property_info *prop); ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *value, uint8_t value_type, bool strict); ZEND_API zval* zend_assign_to_typed_ref_ex(zval *variable_ptr, zval *value, uint8_t value_type, bool strict, zend_refcounted **garbage_ptr); +zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_track_object_mutation_slow(zend_object *zobj); +zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_track_object_mutation_with_value_slow(zend_object *zobj, zval *value); + +#define ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj) do { \ + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { \ + zend_track_object_mutation_slow((zobj)); \ + } \ +} while (0) + +#define ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value) do { \ + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { \ + zend_track_object_mutation_with_value_slow((zobj), (value)); \ + } \ +} while (0) + +static zend_always_inline bool zend_maybe_track_hash_mutation(HashTable *ht, bool publish) +{ + if (UNEXPECTED(zend_tracked_hash_mutation_hook != NULL)) { + return zend_tracked_hash_mutation_hook(ht, publish); + } + + return false; +} + static zend_always_inline void zend_copy_to_variable(zval *variable_ptr, const zval *value, uint8_t value_type) { zend_refcounted *ref = NULL; @@ -161,15 +184,27 @@ static zend_always_inline void zend_copy_to_variable(zval *variable_ptr, const z } } +static zend_always_inline void zend_maybe_track_reference_update(zend_reference *updated_ref) +{ + if (UNEXPECTED(updated_ref != NULL && zend_tracked_reference_update_hook != NULL && EG(exception) == NULL)) { + zend_tracked_reference_update_hook(updated_ref); + } +} + static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval *value, uint8_t value_type, bool strict) { + zend_reference *updated_ref = NULL; + do { if (UNEXPECTED(Z_REFCOUNTED_P(variable_ptr))) { zend_refcounted *garbage; if (Z_ISREF_P(variable_ptr)) { + updated_ref = Z_REF_P(variable_ptr); if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(variable_ptr)))) { - return zend_assign_to_typed_ref(variable_ptr, value, value_type, strict); + variable_ptr = zend_assign_to_typed_ref(variable_ptr, value, value_type, strict); + zend_maybe_track_reference_update(updated_ref); + return variable_ptr; } variable_ptr = Z_REFVAL_P(variable_ptr); @@ -179,22 +214,29 @@ static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval } garbage = Z_COUNTED_P(variable_ptr); zend_copy_to_variable(variable_ptr, value, value_type); + zend_maybe_track_reference_update(updated_ref); GC_DTOR_NO_REF(garbage); return variable_ptr; } } while (0); zend_copy_to_variable(variable_ptr, value, value_type); + zend_maybe_track_reference_update(updated_ref); return variable_ptr; } static zend_always_inline zval* zend_assign_to_variable_ex(zval *variable_ptr, zval *value, zend_uchar value_type, bool strict, zend_refcounted **garbage_ptr) { + zend_reference *updated_ref = NULL; + do { if (UNEXPECTED(Z_REFCOUNTED_P(variable_ptr))) { if (Z_ISREF_P(variable_ptr)) { + updated_ref = Z_REF_P(variable_ptr); if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(variable_ptr)))) { - return zend_assign_to_typed_ref_ex(variable_ptr, value, value_type, strict, garbage_ptr); + variable_ptr = zend_assign_to_typed_ref_ex(variable_ptr, value, value_type, strict, garbage_ptr); + zend_maybe_track_reference_update(updated_ref); + return variable_ptr; } variable_ptr = Z_REFVAL_P(variable_ptr); @@ -207,9 +249,20 @@ static zend_always_inline zval* zend_assign_to_variable_ex(zval *variable_ptr, z } while (0); zend_copy_to_variable(variable_ptr, value, value_type); + zend_maybe_track_reference_update(updated_ref); return variable_ptr; } +static zend_always_inline void zend_class_static_update(zend_class_entry *ce) +{ + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_class_static_update_hook != NULL && + EG(exception) == NULL) + ) { + zend_class_static_update_hook(ce); + } +} + static zend_always_inline void zend_safe_assign_to_variable_noref(zval *variable_ptr, const zval *value) { if (Z_REFCOUNTED_P(variable_ptr)) { ZEND_ASSERT(Z_TYPE_P(variable_ptr) != IS_REFERENCE); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 8257df32e831..bae276f93ca2 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -268,6 +268,10 @@ struct _zend_executor_globals { struct _zend_module_entry *current_module; bool active; + + bool static_cache_class_access_active; /* fast guard for zend_class_static_access_hook */ + bool tracked_mutation_hooks_active; /* fast guard for tracked array/object mutation hooks */ + uint8_t flags; zend_long assertions; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 571aa9e92abc..bfc37c672e14 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -2094,6 +2094,10 @@ ZEND_API void zend_class_init_statics(zend_class_entry *class_type) /* {{{ */ ZVAL_COPY_OR_DUP(&CE_STATIC_MEMBERS(class_type)[i], p); } } + + if (UNEXPECTED(zend_class_init_statics_hook != NULL)) { + zend_class_init_statics_hook(class_type); + } } } /* }}} */ @@ -2139,6 +2143,12 @@ ZEND_API zval *zend_std_get_static_property_with_info(zend_class_entry *ce, zend zend_class_init_statics(ce); } + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_class_static_access_hook != NULL) + ) { + zend_class_static_access_hook(ce); + } + ret = CE_STATIC_MEMBERS(ce) + property_info->offset; ZVAL_DEINDIRECT(ret); diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index ccad24682fdb..d521458e9f3f 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -452,6 +452,156 @@ extern "C++" { # endif #endif /* ZEND_DEBUG */ +#ifdef ZTS + +typedef void *(*zend_thread_routine_t)(void *arg); + +#ifdef ZEND_WIN32 + +#include +#include + +typedef struct _zend_thread { + HANDLE handle; + unsigned id; +} zend_thread_t; + +typedef SRWLOCK zend_thread_rwlock_t; + +typedef struct _zend_thread_start_ctx { + zend_thread_routine_t routine; + void *arg; +} zend_thread_start_ctx; + +static unsigned __stdcall zend_thread_trampoline(void *arg) +{ + zend_thread_start_ctx *ctx = (zend_thread_start_ctx *) arg; + zend_thread_routine_t routine = ctx->routine; + void *routine_arg = ctx->arg; + + routine(routine_arg); + free(ctx); + return 0; +} + +static zend_always_inline bool zend_thread_start(zend_thread_t *thread, zend_thread_routine_t routine, void *arg) +{ + zend_thread_start_ctx *ctx; + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) { + return false; + } + + ctx->routine = routine; + ctx->arg = arg; + thread->handle = (HANDLE) _beginthreadex(NULL, 0, zend_thread_trampoline, ctx, 0, &thread->id); + if (thread->handle == NULL) { + free(ctx); + return false; + } + + return true; +} + +static zend_always_inline bool zend_thread_join(zend_thread_t *thread) +{ + DWORD wait_status; + + wait_status = WaitForSingleObject(thread->handle, INFINITE); + if (thread->handle != NULL) { + CloseHandle(thread->handle); + thread->handle = NULL; + } + + return wait_status == WAIT_OBJECT_0; +} + +static zend_always_inline bool zend_thread_rwlock_init(zend_thread_rwlock_t *lock) +{ + InitializeSRWLock(lock); + return true; +} + +static zend_always_inline bool zend_thread_rwlock_rdlock(zend_thread_rwlock_t *lock) +{ + AcquireSRWLockShared(lock); + return true; +} + +static zend_always_inline bool zend_thread_rwlock_wrlock(zend_thread_rwlock_t *lock) +{ + AcquireSRWLockExclusive(lock); + return true; +} + +static zend_always_inline bool zend_thread_rwlock_unlock_rd(zend_thread_rwlock_t *lock) +{ + ReleaseSRWLockShared(lock); + return true; +} + +static zend_always_inline bool zend_thread_rwlock_unlock_wr(zend_thread_rwlock_t *lock) +{ + ReleaseSRWLockExclusive(lock); + return true; +} + +static zend_always_inline void zend_thread_rwlock_destroy(zend_thread_rwlock_t *lock) +{ + ZEND_IGNORE_VALUE(lock); + return; +} + +#else + +typedef pthread_t zend_thread_t; +typedef pthread_rwlock_t zend_thread_rwlock_t; + +static zend_always_inline bool zend_thread_start(zend_thread_t *thread, zend_thread_routine_t routine, void *arg) +{ + return pthread_create(thread, NULL, routine, arg) == 0; +} + +static zend_always_inline bool zend_thread_join(zend_thread_t *thread) +{ + return pthread_join(*thread, NULL) == 0; +} + +static zend_always_inline bool zend_thread_rwlock_init(zend_thread_rwlock_t *lock) +{ + return pthread_rwlock_init(lock, NULL) == 0; +} + +static zend_always_inline bool zend_thread_rwlock_rdlock(zend_thread_rwlock_t *lock) +{ + return pthread_rwlock_rdlock(lock) == 0; +} + +static zend_always_inline bool zend_thread_rwlock_wrlock(zend_thread_rwlock_t *lock) +{ + return pthread_rwlock_wrlock(lock) == 0; +} + +static zend_always_inline bool zend_thread_rwlock_unlock_rd(zend_thread_rwlock_t *lock) +{ + return pthread_rwlock_unlock(lock) == 0; +} + +static zend_always_inline bool zend_thread_rwlock_unlock_wr(zend_thread_rwlock_t *lock) +{ + return pthread_rwlock_unlock(lock) == 0; +} + +static zend_always_inline void zend_thread_rwlock_destroy(zend_thread_rwlock_t *lock) +{ + pthread_rwlock_destroy(lock); +} + +#endif + +#endif + #ifdef PHP_HAVE_BUILTIN_EXPECT # define EXPECTED(condition) __builtin_expect(!!(condition), 1) # define UNEXPECTED(condition) __builtin_expect(!!(condition), 0) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 1de7a7cd4195..c485a4be53b3 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -1014,7 +1014,7 @@ ZEND_VM_HANDLER(28, ZEND_ASSIGN_OBJ_OP, VAR|UNUSED|THIS|CV, CONST|TMP|CV, OP) void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -1089,6 +1089,8 @@ ZEND_VM_C_LABEL(assign_op_object): } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP_DATA(); FREE_OP2(); FREE_OP1(); @@ -1148,6 +1150,7 @@ ZEND_VM_HANDLER(29, ZEND_ASSIGN_STATIC_PROP_OP, ANY, ANY, OP) } FREE_OP_DATA(); + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -1158,12 +1161,16 @@ ZEND_VM_HANDLER(27, ZEND_ASSIGN_DIM_OP, VAR|CV, CONST|TMP|UNUSED|NEXT|CV, OP) zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_RW); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { ZEND_VM_C_LABEL(assign_dim_op_array): + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); ZEND_VM_C_LABEL(assign_dim_op_new_array): @@ -1203,6 +1210,9 @@ ZEND_VM_C_LABEL(assign_dim_op_new_array): ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -1219,6 +1229,7 @@ ZEND_VM_C_LABEL(assign_dim_op_new_array): dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -1293,7 +1304,7 @@ ZEND_VM_HANDLER(132, ZEND_PRE_INC_OBJ, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -1345,6 +1356,8 @@ ZEND_VM_C_LABEL(pre_incdec_object): } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP2(); FREE_OP1(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -1364,7 +1377,7 @@ ZEND_VM_HANDLER(134, ZEND_POST_INC_OBJ, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -1414,6 +1427,8 @@ ZEND_VM_C_LABEL(post_incdec_object): } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP2(); FREE_OP1(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -2490,7 +2505,7 @@ ZEND_VM_HANDLER(24, ZEND_ASSIGN_OBJ, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE_SLO { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -2630,6 +2645,7 @@ ZEND_VM_C_LABEL(free_and_exit_assign_obj): } FREE_OP_DATA(); ZEND_VM_C_LABEL(exit_assign_obj): + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -2673,6 +2689,7 @@ ZEND_VM_HANDLER(25, ZEND_ASSIGN_STATIC_PROP, ANY, ANY, CACHE_SLOT, SPEC(OP_DATA= GC_DTOR_NO_REF(garbage); } + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -2685,12 +2702,16 @@ ZEND_VM_HANDLER(23, ZEND_ASSIGN_DIM, VAR|CV, CONST|TMP|UNUSED|NEXT|CV, SPEC(OP_D zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { ZEND_VM_C_LABEL(try_assign_dim_array): + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (OP2_TYPE == IS_UNUSED) { value = GET_OP_DATA_ZVAL_PTR_UNDEF(BP_VAR_R); @@ -2748,6 +2769,9 @@ ZEND_VM_C_LABEL(try_assign_dim_array): if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -2774,6 +2798,7 @@ ZEND_VM_C_LABEL(try_assign_dim_array): } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); FREE_OP_DATA(); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -2902,6 +2927,7 @@ ZEND_VM_HANDLER(32, ZEND_ASSIGN_OBJ_REF, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -2909,6 +2935,11 @@ ZEND_VM_HANDLER(32, ZEND_ASSIGN_OBJ_REF, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE property = GET_OP2_ZVAL_PTR(BP_VAR_R); value_ptr = GET_OP_DATA_ZVAL_PTR_PTR(BP_VAR_W); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (ZEND_VM_SPEC) { if (OP1_TYPE == IS_UNUSED) { @@ -2928,6 +2959,7 @@ ZEND_VM_HANDLER(32, ZEND_ASSIGN_OBJ_REF, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE zend_assign_to_property_reference(container, OP1_TYPE, property, OP2_TYPE, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); FREE_OP1(); FREE_OP2(); FREE_OP_DATA(); @@ -2980,6 +3012,7 @@ ZEND_VM_HANDLER(33, ZEND_ASSIGN_STATIC_PROP_REF, ANY, ANY, CACHE_SLOT|SRC) } FREE_OP_DATA(); + zend_class_static_update(prop_info->ce); ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -6773,6 +6806,8 @@ ZEND_VM_HANDLER(75, ZEND_UNSET_DIM, VAR|CV, CONST|TMP|CV) zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_UNSET); @@ -6783,6 +6818,9 @@ ZEND_VM_HANDLER(75, ZEND_UNSET_DIM, VAR|CV, CONST|TMP|CV) HashTable *ht; ZEND_VM_C_LABEL(unset_dim_array): + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); ZEND_VM_C_LABEL(offset_again): @@ -6796,10 +6834,12 @@ ZEND_VM_C_LABEL(offset_again): ZEND_VM_C_LABEL(str_index_dim): ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); ZEND_VM_C_LABEL(num_index_dim): zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((OP2_TYPE & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); ZEND_VM_C_GOTO(offset_again); @@ -6836,6 +6876,9 @@ ZEND_VM_C_LABEL(num_index_dim): } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -6854,6 +6897,7 @@ ZEND_VM_C_LABEL(num_index_dim): offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -6874,6 +6918,7 @@ ZEND_VM_HANDLER(76, ZEND_UNSET_OBJ, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE_SLOT zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_UNSET); @@ -6894,6 +6939,7 @@ ZEND_VM_HANDLER(76, ZEND_UNSET_OBJ, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE_SLOT break; } } + zobj = Z_OBJ_P(container); if (OP2_TYPE == IS_CONST) { name = Z_STR_P(offset); } else { @@ -6908,6 +6954,8 @@ ZEND_VM_HANDLER(76, ZEND_UNSET_OBJ, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE_SLOT } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP2(); FREE_OP1(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -9219,6 +9267,10 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, ANY, REF) SAVE_OPLINE(); + if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + zend_function_init_statics_hook(execute_data); + } + ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr); if (!ht) { ht = zend_array_dup(EX(func)->op_array.static_variables); @@ -9270,6 +9322,10 @@ ZEND_VM_HANDLER(203, ZEND_BIND_INIT_STATIC_OR_JMP, CV, JMP_ADDR) variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W); + if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + zend_function_init_statics_hook(execute_data); + } + ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr); if (!ht) { ZEND_VM_NEXT_OPCODE(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 5b52f1941845..07d31fcf2e80 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -827,6 +827,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -1020,6 +1021,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC GC_DTOR_NO_REF(garbage); } + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -1057,6 +1059,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC GC_DTOR_NO_REF(garbage); } + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -1096,6 +1099,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC GC_DTOR_NO_REF(garbage); } + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -1145,6 +1149,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + zend_class_static_update(prop_info->ce); ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -23912,7 +23917,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -23987,6 +23992,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -24002,12 +24009,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -24047,6 +24058,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -24063,6 +24077,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -24137,7 +24152,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -24189,6 +24204,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -24203,7 +24220,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -24253,6 +24270,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -24439,7 +24458,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -24581,6 +24600,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -24596,7 +24616,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -24736,6 +24756,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -24751,7 +24772,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -24893,6 +24914,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -24912,12 +24934,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -24975,6 +25001,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -25001,6 +25030,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -25070,12 +25100,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -25133,6 +25167,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -25159,6 +25196,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -25224,12 +25262,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -25287,6 +25329,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -25313,6 +25358,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -25436,6 +25482,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -25443,6 +25490,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -25462,6 +25514,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_VAR, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -25474,6 +25527,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -25481,6 +25535,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -25500,6 +25559,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_VAR, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -26223,6 +26283,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -26233,6 +26295,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -26246,10 +26311,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_CONST & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -26286,6 +26353,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -26304,6 +26374,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -26324,6 +26395,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -26344,6 +26416,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_CONST == IS_CONST) { name = Z_STR_P(offset); } else { @@ -26358,6 +26431,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -26627,7 +26702,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -26702,6 +26777,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -26716,12 +26793,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -26761,6 +26842,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -26777,6 +26861,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -26851,7 +26936,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -26903,6 +26988,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -26917,7 +27004,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -26967,6 +27054,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -27147,7 +27236,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -27289,6 +27378,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -27303,7 +27393,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -27443,6 +27533,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -27457,7 +27548,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -27599,6 +27690,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -27617,12 +27709,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -27680,6 +27776,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -27706,6 +27805,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -27774,12 +27874,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -27837,6 +27941,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -27863,6 +27970,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -27927,12 +28035,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -27990,6 +28102,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -28016,6 +28131,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -28138,6 +28254,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -28145,6 +28262,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -28164,6 +28286,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_VAR, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); @@ -28175,6 +28298,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -28182,6 +28306,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -28201,6 +28330,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_VAR, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -28481,6 +28611,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -28491,6 +28623,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -28504,10 +28639,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_TMP_VAR & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -28544,6 +28681,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -28562,6 +28702,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -28582,6 +28723,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -28602,6 +28744,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_TMP_VAR == IS_CONST) { name = Z_STR_P(offset); } else { @@ -28616,6 +28759,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -28787,12 +28932,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -28832,6 +28981,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -28848,6 +29000,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -28947,12 +29100,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -29010,6 +29167,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -29036,6 +29196,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -29105,12 +29266,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -29168,6 +29333,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -29194,6 +29362,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -29259,12 +29428,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -29322,6 +29495,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -29348,6 +29524,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -30444,7 +30621,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -30519,6 +30696,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -30534,12 +30713,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -30579,6 +30762,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -30595,6 +30781,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -30669,7 +30856,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -30721,6 +30908,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -30735,7 +30924,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -30785,6 +30974,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -30971,7 +31162,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -31113,6 +31304,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -31128,7 +31320,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -31268,6 +31460,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -31283,7 +31476,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -31425,6 +31618,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -31444,12 +31638,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -31507,6 +31705,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -31533,6 +31734,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -31602,12 +31804,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -31665,6 +31871,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -31691,6 +31900,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -31756,12 +31966,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -31819,6 +32033,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -31845,6 +32062,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -32007,6 +32225,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -32014,6 +32233,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -32033,6 +32257,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_VAR, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -32045,6 +32270,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -32052,6 +32278,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -32071,6 +32302,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_VAR, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -32358,6 +32590,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -32368,6 +32602,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -32381,10 +32618,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_CV & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -32421,6 +32660,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -32439,6 +32681,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -32459,6 +32702,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -32479,6 +32723,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_CV == IS_CONST) { name = Z_STR_P(offset); } else { @@ -32493,6 +32738,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -32897,7 +33144,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -32972,6 +33219,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -32991,7 +33240,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -33043,6 +33292,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -33058,7 +33309,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -33108,6 +33359,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -33505,7 +33758,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -33647,6 +33900,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -33663,7 +33917,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -33803,6 +34057,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -33819,7 +34074,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -33961,6 +34216,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -33977,6 +34233,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -33984,6 +34241,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -34003,6 +34265,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -34015,6 +34279,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -34022,6 +34287,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -34041,6 +34311,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -34649,6 +34921,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = &EX(This); @@ -34669,6 +34942,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_CONST == IS_CONST) { name = Z_STR_P(offset); } else { @@ -34683,6 +34957,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -35006,7 +35282,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -35081,6 +35357,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -35099,7 +35377,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -35151,6 +35429,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -35166,7 +35446,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -35216,6 +35496,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -35603,7 +35885,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -35745,6 +36027,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -35760,7 +36043,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -35900,6 +36183,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -35915,7 +36199,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -36057,6 +36341,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -36072,6 +36357,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -36079,6 +36365,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -36098,6 +36389,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); @@ -36109,6 +36402,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -36116,6 +36410,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -36135,6 +36434,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -36540,6 +36841,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = &EX(This); @@ -36560,6 +36862,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_TMP_VAR == IS_CONST) { name = Z_STR_P(offset); } else { @@ -36574,6 +36877,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -37569,7 +37874,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -37644,6 +37949,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -37663,7 +37970,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -37715,6 +38022,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -37730,7 +38039,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -37780,6 +38089,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -38172,7 +38483,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -38314,6 +38625,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -38330,7 +38642,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -38470,6 +38782,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -38486,7 +38799,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -38628,6 +38941,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -38644,6 +38958,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -38651,6 +38966,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -38670,6 +38990,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -38682,6 +39004,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -38689,6 +39012,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -38708,6 +39036,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -39125,6 +39455,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = &EX(This); @@ -39145,6 +39476,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_CV == IS_CONST) { name = Z_STR_P(offset); } else { @@ -39159,6 +39491,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -40898,6 +41232,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_BIND_STATIC_S SAVE_OPLINE(); + if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + zend_function_init_statics_hook(execute_data); + } + ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr); if (!ht) { ht = zend_array_dup(EX(func)->op_array.static_variables); @@ -40949,6 +41287,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_BIND_INIT_STA variable_ptr = EX_VAR(opline->op1.var); + if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + zend_function_init_statics_hook(execute_data); + } + ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr); if (!ht) { ZEND_VM_NEXT_OPCODE(); @@ -41668,7 +42010,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -41743,6 +42085,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -41759,12 +42103,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -41804,6 +42152,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -41820,6 +42171,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -41896,7 +42248,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -41948,6 +42300,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -41963,7 +42317,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -42013,6 +42367,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -42541,7 +42897,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -42683,6 +43039,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -42699,7 +43056,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -42839,6 +43196,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -42855,7 +43213,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -42997,6 +43355,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -43017,12 +43376,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -43080,6 +43443,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -43106,6 +43472,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -43176,12 +43543,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -43239,6 +43610,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -43265,6 +43639,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -43331,12 +43706,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -43394,6 +43773,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -43420,6 +43802,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -43546,6 +43929,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -43553,6 +43937,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -43572,6 +43961,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_CV, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -43584,6 +43975,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -43591,6 +43983,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -43610,6 +44007,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_CV, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -44196,6 +44595,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -44206,6 +44607,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -44219,10 +44623,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_CONST & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -44259,6 +44665,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -44277,6 +44686,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -44298,6 +44708,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -44318,6 +44729,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_CONST == IS_CONST) { name = Z_STR_P(offset); } else { @@ -44332,6 +44744,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -45507,7 +45921,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -45582,6 +45996,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -45597,12 +46013,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -45642,6 +46062,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -45658,6 +46081,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -45734,7 +46158,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -45786,6 +46210,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -45801,7 +46227,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -45851,6 +46277,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -46364,7 +46792,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -46506,6 +46934,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -46521,7 +46950,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -46661,6 +47090,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -46676,7 +47106,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -46818,6 +47248,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -46837,12 +47268,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -46900,6 +47335,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -46926,6 +47364,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -46995,12 +47434,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -47058,6 +47501,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -47084,6 +47530,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -47149,12 +47596,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -47212,6 +47663,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -47238,6 +47692,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -47363,6 +47818,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -47370,6 +47826,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -47389,6 +47850,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_CV, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); @@ -47400,6 +47863,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -47407,6 +47871,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -47426,6 +47895,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_CV, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -47858,6 +48329,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -47868,6 +48341,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -47881,10 +48357,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_TMP_VAR & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -47921,6 +48399,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -47939,6 +48420,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -47960,6 +48442,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -47980,6 +48463,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_TMP_VAR == IS_CONST) { name = Z_STR_P(offset); } else { @@ -47994,6 +48478,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -48380,12 +48866,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -48425,6 +48915,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -48441,6 +48934,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -48678,12 +49172,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -48741,6 +49239,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -48767,6 +49268,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -48837,12 +49339,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -48900,6 +49406,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -48926,6 +49435,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -48992,12 +49502,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -49055,6 +49569,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -49081,6 +49598,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -50612,7 +51130,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -50687,6 +51205,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_OP } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -50703,12 +51223,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -50748,6 +51272,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -50764,6 +51291,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_OP dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -50840,7 +51368,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -50892,6 +51420,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_PRE_INC_OBJ_S } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -50907,7 +51437,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -50957,6 +51487,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_POST_INC_OBJ_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -51480,7 +52012,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -51622,6 +52154,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -51638,7 +52171,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -51778,6 +52311,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -51794,7 +52328,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -51936,6 +52470,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -51956,12 +52491,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -52019,6 +52558,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -52045,6 +52587,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -52115,12 +52658,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -52178,6 +52725,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -52204,6 +52754,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -52270,12 +52821,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -52333,6 +52888,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -52359,6 +52917,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -52525,6 +53084,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -52532,6 +53092,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -52551,6 +53116,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_CV, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -52563,6 +53130,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -52570,6 +53138,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -52589,6 +53162,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_RE zend_assign_to_property_reference(container, IS_CV, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -53029,6 +53604,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -53039,6 +53616,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -53052,10 +53632,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_CV & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -53092,6 +53674,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -53110,6 +53695,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -53131,6 +53717,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -53151,6 +53738,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE break; } } + zobj = Z_OBJ_P(container); if (IS_CV == IS_CONST) { name = Z_STR_P(offset); } else { @@ -53165,6 +53753,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_OBJ_SPE } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -53651,6 +54241,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -53810,6 +54401,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP GC_DTOR_NO_REF(garbage); } + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -53847,6 +54439,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP GC_DTOR_NO_REF(garbage); } + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -53886,6 +54479,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP GC_DTOR_NO_REF(garbage); } + zend_class_static_update(prop_info->ce); /* assign_static_prop has two opcodes! */ ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -53935,6 +54529,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + zend_class_static_update(prop_info->ce); ZEND_VM_NEXT_OPCODE_EX(1, 2); } @@ -76384,7 +76979,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -76459,6 +77054,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -76474,12 +77071,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -76519,6 +77120,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -76535,6 +77139,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -76609,7 +77214,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -76661,6 +77266,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -76675,7 +77282,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -76725,6 +77332,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -76911,7 +77520,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -77053,6 +77662,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -77068,7 +77678,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -77208,6 +77818,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -77223,7 +77834,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -77365,6 +77976,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -77384,12 +77996,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -77447,6 +78063,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -77473,6 +78092,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -77542,12 +78162,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -77605,6 +78229,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -77631,6 +78258,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -77696,12 +78324,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -77759,6 +78391,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -77785,6 +78420,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -77908,6 +78544,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -77915,6 +78552,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -77934,6 +78576,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_VAR, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -77946,6 +78589,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -77953,6 +78597,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -77972,6 +78621,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_VAR, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -78695,6 +79345,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -78705,6 +79357,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -78718,10 +79373,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_CONST & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -78758,6 +79415,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -78776,6 +79436,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -78796,6 +79457,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -78816,6 +79478,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR break; } } + zobj = Z_OBJ_P(container); if (IS_CONST == IS_CONST) { name = Z_STR_P(offset); } else { @@ -78830,6 +79493,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -79099,7 +79764,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -79174,6 +79839,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -79188,12 +79855,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -79233,6 +79904,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -79249,6 +79923,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -79323,7 +79998,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -79375,6 +80050,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -79389,7 +80066,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -79439,6 +80116,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -79619,7 +80298,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -79761,6 +80440,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -79775,7 +80455,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -79915,6 +80595,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -79929,7 +80610,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -80071,6 +80752,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -80089,12 +80771,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -80152,6 +80838,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -80178,6 +80867,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -80246,12 +80936,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -80309,6 +81003,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -80335,6 +81032,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -80399,12 +81097,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -80462,6 +81164,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -80488,6 +81193,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -80610,6 +81316,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -80617,6 +81324,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -80636,6 +81348,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_VAR, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); @@ -80647,6 +81360,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -80654,6 +81368,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -80673,6 +81392,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_VAR, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -80953,6 +81673,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -80963,6 +81685,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -80976,10 +81701,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_TMP_VAR & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -81016,6 +81743,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -81034,6 +81764,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -81054,6 +81785,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -81074,6 +81806,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR break; } } + zobj = Z_OBJ_P(container); if (IS_TMP_VAR == IS_CONST) { name = Z_STR_P(offset); } else { @@ -81088,6 +81821,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -81259,12 +81994,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -81304,6 +82043,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -81320,6 +82062,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -81419,12 +82162,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -81482,6 +82229,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -81508,6 +82258,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -81577,12 +82328,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -81640,6 +82395,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -81666,6 +82424,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -81731,12 +82490,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -81794,6 +82557,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -81820,6 +82586,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -82916,7 +83683,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -82991,6 +83758,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -83006,12 +83775,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -83051,6 +83824,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -83067,6 +83843,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -83141,7 +83918,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -83193,6 +83970,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_V } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -83207,7 +83986,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -83257,6 +84036,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -83443,7 +84224,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -83585,6 +84366,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -83600,7 +84382,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -83740,6 +84522,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -83755,7 +84538,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -83897,6 +84680,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -83916,12 +84700,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -83979,6 +84767,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -84005,6 +84796,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -84074,12 +84866,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -84137,6 +84933,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -84163,6 +84962,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -84228,12 +85028,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -84291,6 +85095,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -84317,6 +85124,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -84479,6 +85287,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -84486,6 +85295,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -84505,6 +85319,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_VAR, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -84517,6 +85332,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -84524,6 +85340,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_VAR == IS_UNUSED) { @@ -84543,6 +85364,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_VAR, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); @@ -84830,6 +85652,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -84840,6 +85664,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -84853,10 +85680,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_CV & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -84893,6 +85722,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -84911,6 +85743,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -84931,6 +85764,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -84951,6 +85785,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR break; } } + zobj = Z_OBJ_P(container); if (IS_CV == IS_CONST) { name = Z_STR_P(offset); } else { @@ -84965,6 +85800,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_VAR } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -85369,7 +86206,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -85444,6 +86281,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -85463,7 +86302,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -85515,6 +86354,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -85530,7 +86371,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -85580,6 +86421,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -85977,7 +86820,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -86119,6 +86962,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -86135,7 +86979,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -86275,6 +87119,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -86291,7 +87136,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -86433,6 +87278,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -86449,6 +87295,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -86456,6 +87303,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -86475,6 +87327,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -86487,6 +87341,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -86494,6 +87349,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -86513,6 +87373,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -87121,6 +87983,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = &EX(This); @@ -87141,6 +88004,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU break; } } + zobj = Z_OBJ_P(container); if (IS_CONST == IS_CONST) { name = Z_STR_P(offset); } else { @@ -87155,6 +88019,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -87478,7 +88344,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -87553,6 +88419,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -87571,7 +88439,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -87623,6 +88491,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -87638,7 +88508,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -87688,6 +88558,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -88075,7 +88947,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -88217,6 +89089,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -88232,7 +89105,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -88372,6 +89245,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -88387,7 +89261,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -88529,6 +89403,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -88544,6 +89419,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -88551,6 +89427,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -88570,6 +89451,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); @@ -88581,6 +89464,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -88588,6 +89472,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -88607,6 +89496,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -89012,6 +89903,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = &EX(This); @@ -89032,6 +89924,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU break; } } + zobj = Z_OBJ_P(container); if (IS_TMP_VAR == IS_CONST) { name = Z_STR_P(offset); } else { @@ -89046,6 +89939,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -90041,7 +90936,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -90116,6 +91011,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -90135,7 +91032,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -90187,6 +91084,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_U } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -90202,7 +91101,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -90252,6 +91151,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -90644,7 +91545,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -90786,6 +91687,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -90802,7 +91704,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -90942,6 +91844,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -90958,7 +91861,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -91100,6 +92003,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -91116,6 +92020,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -91123,6 +92028,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -91142,6 +92052,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -91154,6 +92066,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -91161,6 +92074,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_UNUSED == IS_UNUSED) { @@ -91180,6 +92098,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_UNUSED, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -91597,6 +92517,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = &EX(This); @@ -91617,6 +92538,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU break; } } + zobj = Z_OBJ_P(container); if (IS_CV == IS_CONST) { name = Z_STR_P(offset); } else { @@ -91631,6 +92553,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_UNU } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -93370,6 +94294,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_BIND_STATIC_SPEC_C SAVE_OPLINE(); + if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + zend_function_init_statics_hook(execute_data); + } + ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr); if (!ht) { ht = zend_array_dup(EX(func)->op_array.static_variables); @@ -93421,6 +94349,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_BIND_INIT_STATIC_O variable_ptr = EX_VAR(opline->op1.var); + if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + zend_function_init_statics_hook(execute_data); + } + ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr); if (!ht) { ZEND_VM_NEXT_OPCODE(); @@ -94140,7 +95072,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -94215,6 +95147,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -94231,12 +95165,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -94276,6 +95214,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -94292,6 +95233,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -94368,7 +95310,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -94420,6 +95362,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -94435,7 +95379,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -94485,6 +95429,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -95013,7 +95959,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -95155,6 +96101,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -95171,7 +96118,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -95311,6 +96258,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -95327,7 +96275,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -95469,6 +96417,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -95489,12 +96438,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -95552,6 +96505,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -95578,6 +96534,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -95648,12 +96605,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -95711,6 +96672,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -95737,6 +96701,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -95803,12 +96768,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CONST == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -95866,6 +96835,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -95892,6 +96864,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -96018,6 +96991,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -96025,6 +96999,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -96044,6 +97023,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_CV, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -96056,6 +97037,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -96063,6 +97045,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = RT_CONSTANT(opline, opline->op2); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -96082,6 +97069,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_CV, property, IS_CONST, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -96668,6 +97657,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -96678,6 +97669,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -96691,10 +97685,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_CONST & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -96731,6 +97727,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -96749,6 +97748,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -96770,6 +97770,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -96790,6 +97791,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ break; } } + zobj = Z_OBJ_P(container); if (IS_CONST == IS_CONST) { name = Z_STR_P(offset); } else { @@ -96804,6 +97806,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -97979,7 +98983,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -98054,6 +99058,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -98069,12 +99075,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -98114,6 +99124,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -98130,6 +99143,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -98206,7 +99220,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -98258,6 +99272,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -98273,7 +99289,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -98323,6 +99339,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -98836,7 +99854,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -98978,6 +99996,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -98993,7 +100012,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -99133,6 +100152,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -99148,7 +100168,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -99290,6 +100310,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -99309,12 +100330,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -99372,6 +100397,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -99398,6 +100426,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -99467,12 +100496,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -99530,6 +100563,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -99556,6 +100592,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -99621,12 +100658,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_TMP_VAR == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -99684,6 +100725,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -99710,6 +100754,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -99835,6 +100880,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -99842,6 +100888,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -99861,6 +100912,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_CV, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); @@ -99872,6 +100925,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -99879,6 +100933,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -99898,6 +100957,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_CV, property, IS_TMP_VAR, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -100330,6 +101391,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -100340,6 +101403,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -100353,10 +101419,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_TMP_VAR & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -100393,6 +101461,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -100411,6 +101482,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -100432,6 +101504,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -100452,6 +101525,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ break; } } + zobj = Z_OBJ_P(container); if (IS_TMP_VAR == IS_CONST) { name = Z_STR_P(offset); } else { @@ -100466,6 +101540,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + zval_ptr_dtor_nogc(EX_VAR(opline->op2.var)); @@ -100852,12 +101928,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -100897,6 +101977,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -100913,6 +101996,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -101048,12 +102132,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -101111,6 +102199,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -101137,6 +102228,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -101207,12 +102299,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -101270,6 +102366,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -101296,6 +102395,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -101362,12 +102462,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_UNUSED == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -101425,6 +102529,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -101451,6 +102558,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -102982,7 +104090,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -103057,6 +104165,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_OP_SPEC } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); @@ -103073,12 +104183,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC zval *var_ptr; zval *value, *container, *dim; HashTable *ht; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) { assign_dim_op_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); assign_dim_op_new_array: @@ -103118,6 +104232,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); } FREE_OP((opline+1)->op1_type, (opline+1)->op1.var); + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), true); + } } else { if (EXPECTED(Z_ISREF_P(container))) { container = Z_REFVAL_P(container); @@ -103134,6 +104251,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_OP_SPEC dim++; } zend_binary_assign_op_obj_dim(obj, dim OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); } else if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) { uint8_t old_type; @@ -103210,7 +104328,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -103262,6 +104380,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_PRE_INC_OBJ_SPEC_C } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -103277,7 +104397,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ void *_cache_slot[3] = {0}; void **cache_slot; zend_property_info *prop_info; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; SAVE_OPLINE(); @@ -103327,6 +104447,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_POST_INC_OBJ_SPEC_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -103850,7 +104972,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -103992,6 +105114,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -104008,7 +105131,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -104148,6 +105271,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV } zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -104164,7 +105288,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV { USE_OPLINE zval *object, *value, tmp; - zend_object *zobj; + zend_object *zobj = NULL; zend_string *name, *tmp_name; zend_refcounted *garbage = NULL; @@ -104306,6 +105430,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV exit_assign_obj: + ZEND_MAYBE_TRACK_OBJECT_MUTATION_WITH_VALUE(zobj, value); if (garbage) { GC_DTOR_NO_REF(garbage); } @@ -104326,12 +105451,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = RT_CONSTANT((opline+1), (opline+1)->op1); @@ -104389,6 +105518,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -104415,6 +105547,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -104485,12 +105618,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); @@ -104548,6 +105685,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -104574,6 +105714,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -104640,12 +105781,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV zval *variable_ptr; zval *dim; zend_refcounted *garbage = NULL; + bool should_publish_tracked_array = false; SAVE_OPLINE(); orig_object_ptr = object_ptr = EX_VAR(opline->op1.var); if (EXPECTED(Z_TYPE_P(object_ptr) == IS_ARRAY)) { try_assign_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), false); + } SEPARATE_ARRAY(object_ptr); if (IS_CV == IS_UNUSED) { value = EX_VAR((opline+1)->op1.var); @@ -104703,6 +105848,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV if (garbage) { GC_DTOR_NO_REF(garbage); } + if (UNEXPECTED(should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(Z_ARRVAL_P(object_ptr), true); + } } else { if (EXPECTED(Z_ISREF_P(object_ptr))) { object_ptr = Z_REFVAL_P(object_ptr); @@ -104729,6 +105877,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } zend_assign_to_object_dim(obj, dim, value OPLINE_CC EXECUTE_DATA_CC); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(obj); if (UNEXPECTED(GC_DELREF(obj) == 0)) { @@ -104895,6 +106044,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -104902,6 +106052,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -104921,6 +106076,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_CV, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -104933,6 +106090,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE { USE_OPLINE zval *property, *container, *value_ptr; + zend_object *zobj = NULL; SAVE_OPLINE(); @@ -104940,6 +106098,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE property = _get_zval_ptr_cv_BP_VAR_R(opline->op2.var EXECUTE_DATA_CC); value_ptr = _get_zval_ptr_cv_BP_VAR_W((opline+1)->op1.var EXECUTE_DATA_CC); + if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) { + zobj = Z_OBJ_P(container); + } else if (Z_ISREF_P(container) && EXPECTED(Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT)) { + zobj = Z_OBJ_P(Z_REFVAL_P(container)); + } if (1) { if (IS_CV == IS_UNUSED) { @@ -104959,6 +106122,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_REF_SPE zend_assign_to_property_reference(container, IS_CV, property, IS_CV, value_ptr OPLINE_CC EXECUTE_DATA_CC); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + @@ -105399,6 +106564,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ zval *offset; zend_ulong hval; zend_string *key; + bool should_flush_array = false; + bool should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -105409,6 +106576,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ HashTable *ht; unset_dim_array: + if (UNEXPECTED(EG(tracked_mutation_hooks_active))) { + should_publish_tracked_array = zend_maybe_track_hash_mutation(Z_ARRVAL_P(container), false); + } SEPARATE_ARRAY(container); ht = Z_ARRVAL_P(container); offset_again: @@ -105422,10 +106592,12 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ str_index_dim: ZEND_ASSERT(ht != &EG(symbol_table)); zend_hash_del(ht, key); + should_flush_array = true; } else if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { hval = Z_LVAL_P(offset); num_index_dim: zend_hash_index_del(ht, hval); + should_flush_array = true; } else if ((IS_CV & (IS_VAR|IS_CV)) && EXPECTED(Z_TYPE_P(offset) == IS_REFERENCE)) { offset = Z_REFVAL_P(offset); goto offset_again; @@ -105462,6 +106634,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ } else { zend_illegal_array_offset_unset(offset); } + if (UNEXPECTED(should_flush_array && should_publish_tracked_array)) { + zend_maybe_track_hash_mutation(ht, true); + } break; } else if (Z_ISREF_P(container)) { container = Z_REFVAL_P(container); @@ -105480,6 +106655,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ offset++; } Z_OBJ_HT_P(container)->unset_dimension(Z_OBJ_P(container), offset); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(container)); } else if (UNEXPECTED(Z_TYPE_P(container) == IS_STRING)) { zend_throw_error(NULL, "Cannot unset string offsets"); } else if (UNEXPECTED(Z_TYPE_P(container) > IS_FALSE)) { @@ -105501,6 +106677,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ zval *container; zval *offset; zend_string *name, *tmp_name; + zend_object *zobj = NULL; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -105521,6 +106698,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ break; } } + zobj = Z_OBJ_P(container); if (IS_CV == IS_CONST) { name = Z_STR_P(offset); } else { @@ -105535,6 +106713,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_OBJ_SPEC_CV_ } } while (0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(zobj); + diff --git a/ext/date/php_date.c b/ext/date/php_date.c index fc4a18e455c2..18ebabeadc47 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -17,8 +17,10 @@ #include "php_ini.h" #include "ext/standard/info.h" #include "ext/standard/php_versioning.h" +#include "ext/opcache/zend_static_cache.h" #include "php_date.h" #include "zend_attributes.h" +#include "zend_execute.h" #include "zend_interfaces.h" #include "zend_exceptions.h" #include "lib/timelib.h" @@ -2148,6 +2150,84 @@ static zend_object *date_object_clone_interval(zend_object *this_ptr) /* {{{ */ return &new_obj->std; } /* }}} */ +static bool php_date_initialize_from_hash(php_date_obj *dateobj, const HashTable *myht); +static bool php_date_timezone_initialize_from_hash(php_timezone_obj *tzobj, const HashTable *myht); +static void php_date_interval_initialize_from_hash(php_interval_obj *intobj, const HashTable *myht); + +static bool php_date_copy_direct_cache_state( + void *context, + zend_object *old_object, + zend_object *new_object, + zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value) +{ + (void) context; + (void) clone_value; + + if (instanceof_function(old_object->ce, date_ce_date) || + instanceof_function(old_object->ce, date_ce_immutable)) { + php_date_obj *old_obj = php_date_obj_from_obj(old_object); + php_date_obj *new_obj = php_date_obj_from_obj(new_object); + + if (old_obj->time == NULL) { + return true; + } + + new_obj->time = timelib_time_clone(old_obj->time); + + return new_obj->time != NULL; + } + + if (instanceof_function(old_object->ce, date_ce_timezone)) { + php_timezone_obj *old_obj = php_timezone_obj_from_obj(old_object); + php_timezone_obj *new_obj = php_timezone_obj_from_obj(new_object); + + if (!old_obj->initialized) { + return true; + } + + new_obj->type = old_obj->type; + new_obj->initialized = true; + switch (new_obj->type) { + case TIMELIB_ZONETYPE_ID: + new_obj->tzi.tz = old_obj->tzi.tz; + return true; + case TIMELIB_ZONETYPE_OFFSET: + new_obj->tzi.utc_offset = old_obj->tzi.utc_offset; + return true; + case TIMELIB_ZONETYPE_ABBR: + new_obj->tzi.z.utc_offset = old_obj->tzi.z.utc_offset; + new_obj->tzi.z.dst = old_obj->tzi.z.dst; + new_obj->tzi.z.abbr = old_obj->tzi.z.abbr != NULL + ? timelib_strdup(old_obj->tzi.z.abbr) + : NULL; + return old_obj->tzi.z.abbr == NULL || new_obj->tzi.z.abbr != NULL; + default: + return false; + } + } + + if (instanceof_function(old_object->ce, date_ce_interval)) { + php_interval_obj *old_obj = php_interval_obj_from_obj(old_object); + php_interval_obj *new_obj = php_interval_obj_from_obj(new_object); + + new_obj->civil_or_wall = old_obj->civil_or_wall; + new_obj->from_string = old_obj->from_string; + if (old_obj->date_string != NULL) { + new_obj->date_string = zend_string_copy(old_obj->date_string); + } + new_obj->initialized = old_obj->initialized; + if (old_obj->diff != NULL) { + new_obj->diff = timelib_rel_time_clone(old_obj->diff); + + return new_obj->diff != NULL; + } + + return true; + } + + return false; +} + static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n) /* {{{ */ { @@ -2194,6 +2274,134 @@ static void date_interval_object_to_hash(php_interval_obj *intervalobj, HashTabl #undef PHP_DATE_INTERVAL_ADD_PROPERTY } +static void php_date_direct_cache_add_assoc_int64(zval *array_zv, const char *name, int64_t value) +{ +#if PHP_DATE_SIZEOF_LONG == 8 + add_assoc_long(array_zv, name, (zend_long) value); +#else + add_assoc_str(array_zv, name, zend_strpprintf(0, "%lld", (long long) value)); +#endif +} + +static bool php_date_serialize_direct_cache_state(const zval *object, zval *state) +{ + ZVAL_UNDEF(state); + + if (instanceof_function(Z_OBJCE_P(object), date_ce_date) || + instanceof_function(Z_OBJCE_P(object), date_ce_immutable)) { + php_date_obj *dateobj = Z_PHPDATE_P((zval *) object); + + if (dateobj->time == NULL || !dateobj->time->is_localtime) { + return false; + } + + array_init_size(state, 3); + date_object_to_hash(dateobj, Z_ARRVAL_P(state)); + + return true; + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_timezone)) { + php_timezone_obj *tzobj = Z_PHPTIMEZONE_P((zval *) object); + + if (!tzobj->initialized) { + return false; + } + + array_init_size(state, 2); + date_timezone_object_to_hash(tzobj, Z_ARRVAL_P(state)); + + return true; + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_interval)) { + php_interval_obj *intervalobj = Z_PHPINTERVAL_P((zval *) object); + + if (!intervalobj->initialized || intervalobj->diff == NULL) { + return false; + } + + if (intervalobj->from_string) { + if (intervalobj->date_string == NULL) { + return false; + } + + array_init_size(state, 2); + add_assoc_bool(state, "from_string", true); + add_assoc_str(state, "date_string", zend_string_copy(intervalobj->date_string)); + + return true; + } + + array_init_size(state, 18); + add_assoc_long(state, "y", intervalobj->diff->y); + add_assoc_long(state, "m", intervalobj->diff->m); + add_assoc_long(state, "d", intervalobj->diff->d); + add_assoc_long(state, "h", intervalobj->diff->h); + add_assoc_long(state, "i", intervalobj->diff->i); + add_assoc_long(state, "s", intervalobj->diff->s); + add_assoc_double(state, "f", (double) intervalobj->diff->us / 1000000.0); + add_assoc_long(state, "invert", intervalobj->diff->invert); + + if (intervalobj->diff->days != TIMELIB_UNSET) { + php_date_direct_cache_add_assoc_int64(state, "days", intervalobj->diff->days); + } else { + add_assoc_bool(state, "days", false); + } + + add_assoc_bool(state, "from_string", false); + add_assoc_long(state, "weekday", intervalobj->diff->weekday); + add_assoc_long(state, "weekday_behavior", intervalobj->diff->weekday_behavior); + add_assoc_long(state, "first_last_day_of", intervalobj->diff->first_last_day_of); + add_assoc_long(state, "special_type", intervalobj->diff->special.type); + php_date_direct_cache_add_assoc_int64(state, "special_amount", intervalobj->diff->special.amount); + add_assoc_long(state, "have_weekday_relative", intervalobj->diff->have_weekday_relative); + add_assoc_long(state, "have_special_relative", intervalobj->diff->have_special_relative); + add_assoc_long(state, "civil_or_wall", intervalobj->civil_or_wall); + + return true; + } + + return false; +} + +static bool php_date_unserialize_direct_cache_state(zval *object, zval *state) +{ + if (Z_TYPE_P(state) != IS_ARRAY) { + return false; + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_date) || + instanceof_function(Z_OBJCE_P(object), date_ce_immutable)) { + return php_date_initialize_from_hash(Z_PHPDATE_P(object), Z_ARRVAL_P(state)); + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_timezone)) { + return php_date_timezone_initialize_from_hash(Z_PHPTIMEZONE_P(object), Z_ARRVAL_P(state)); + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_interval)) { + php_date_interval_initialize_from_hash(Z_PHPINTERVAL_P(object), Z_ARRVAL_P(state)); + + return !EG(exception); + } + + return false; +} + +const zend_opcache_static_cache_safe_direct_handlers *php_date_get_direct_cache_handlers(void) +{ + static const zend_opcache_static_cache_safe_direct_handlers handlers = { + true, + php_date_copy_direct_cache_state, + NULL, + php_date_serialize_direct_cache_state, + php_date_unserialize_direct_cache_state + }; + + return &handlers; +} + static HashTable *date_object_get_properties_interval(zend_object *object) /* {{{ */ { HashTable *props; @@ -2824,7 +3032,7 @@ PHP_METHOD(DateTimeImmutable, createFromTimestamp) } /* }}} */ -static bool php_date_initialize_from_hash(php_date_obj **dateobj, const HashTable *myht) +static bool php_date_initialize_from_hash(php_date_obj *dateobj, const HashTable *myht) { zval *z_date; zval *z_timezone_type; @@ -2853,7 +3061,7 @@ static bool php_date_initialize_from_hash(php_date_obj **dateobj, const HashTabl zend_string *tmp = zend_string_concat3( Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), " ", 1, Z_STRVAL_P(z_timezone), Z_STRLEN_P(z_timezone)); - bool ret = php_date_initialize(*dateobj, ZSTR_VAL(tmp), ZSTR_LEN(tmp), NULL, NULL, 0); + bool ret = php_date_initialize(dateobj, ZSTR_VAL(tmp), ZSTR_LEN(tmp), NULL, NULL, 0); zend_string_release(tmp); return ret; } @@ -2873,7 +3081,7 @@ static bool php_date_initialize_from_hash(php_date_obj **dateobj, const HashTabl tzobj->tzi.tz = tzi; tzobj->initialized = true; - ret = php_date_initialize(*dateobj, Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), NULL, &tmp_obj, 0); + ret = php_date_initialize(dateobj, Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), NULL, &tmp_obj, 0); zval_ptr_dtor(&tmp_obj); return ret; } @@ -2896,7 +3104,7 @@ PHP_METHOD(DateTime, __set_state) php_date_instantiate(date_ce_date, return_value); dateobj = Z_PHPDATE_P(return_value); - if (!php_date_initialize_from_hash(&dateobj, myht)) { + if (!php_date_initialize_from_hash(dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTime object"); RETURN_THROWS(); } @@ -2918,7 +3126,7 @@ PHP_METHOD(DateTimeImmutable, __set_state) php_date_instantiate(date_ce_immutable, return_value); dateobj = Z_PHPDATE_P(return_value); - if (!php_date_initialize_from_hash(&dateobj, myht)) { + if (!php_date_initialize_from_hash(dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object"); RETURN_THROWS(); } @@ -3003,7 +3211,7 @@ PHP_METHOD(DateTime, __unserialize) dateobj = Z_PHPDATE_P(object); - if (!php_date_initialize_from_hash(&dateobj, myht)) { + if (!php_date_initialize_from_hash(dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTime object"); RETURN_THROWS(); } @@ -3025,7 +3233,7 @@ PHP_METHOD(DateTimeImmutable, __unserialize) dateobj = Z_PHPDATE_P(object); - if (!php_date_initialize_from_hash(&dateobj, myht)) { + if (!php_date_initialize_from_hash(dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object"); RETURN_THROWS(); } @@ -3048,7 +3256,7 @@ static void php_do_date_time_wakeup(INTERNAL_FUNCTION_PARAMETERS, const char *cl myht = Z_OBJPROP_P(object); - if (!php_date_initialize_from_hash(&dateobj, myht)) { + if (!php_date_initialize_from_hash(dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for %s object", class_name); RETURN_THROWS(); } @@ -3305,6 +3513,7 @@ static bool php_date_modify(zval *object, char *modify, size_t modify_len) /* {{ timelib_update_from_sse(dateobj->time); dateobj->time->have_relative = 0; memset(&dateobj->time->relative, 0, sizeof(dateobj->time->relative)); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); return true; } /* }}} */ @@ -3399,6 +3608,7 @@ static void php_date_add(zval *object, zval *interval, zval *return_value) /* {{ } timelib_time_dtor(dateobj->time); dateobj->time = new_time; + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ /* {{{ Adds an interval to the current date in object. */ @@ -3456,6 +3666,7 @@ static void php_date_sub(zval *object, zval *interval, zval *return_value) /* {{ } timelib_time_dtor(dateobj->time); dateobj->time = new_time; + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ /* {{{ Subtracts an interval to the current date in object. */ @@ -3581,6 +3792,7 @@ static void php_date_timezone_set(zval *object, zval *timezone_object, zval *ret break; } timelib_unixtime2local(dateobj->time, dateobj->time->sse); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ /* {{{ Sets the timezone for the DateTime object. */ @@ -3662,6 +3874,7 @@ static void php_date_time_set(zval *object, zend_long h, zend_long i, zend_long dateobj->time->us = ms; timelib_update_ts(dateobj->time, NULL); timelib_update_from_sse(dateobj->time); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ /* {{{ Sets the time. */ @@ -3712,6 +3925,7 @@ static void php_date_date_set(zval *object, zend_long y, zend_long m, zend_long dateobj->time->m = m; dateobj->time->d = d; timelib_update_ts(dateobj->time, NULL); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ /* {{{ Sets the date. */ @@ -3764,6 +3978,7 @@ static void php_date_isodate_set(zval *object, zend_long y, zend_long w, zend_lo dateobj->time->have_relative = 1; timelib_update_ts(dateobj->time, NULL); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ /* {{{ Sets the ISO date. */ @@ -3812,6 +4027,7 @@ static void php_date_timestamp_set(zval *object, zend_long timestamp, zval *retu timelib_unixtime2local(dateobj->time, (timelib_sll)timestamp); timelib_update_ts(dateobj->time, NULL); php_date_set_time_fraction(dateobj->time, 0); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ /* {{{ Sets the date and time based on an Unix timestamp. */ @@ -3907,6 +4123,7 @@ PHP_METHOD(DateTime, setMicrosecond) dateobj = Z_PHPDATE_P(object); DATE_CHECK_INITIALIZED(dateobj->time, Z_OBJCE_P(object)); php_date_set_time_fraction(dateobj->time, (int)us); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); RETURN_OBJ_COPY(Z_OBJ_P(object)); } @@ -4060,16 +4277,15 @@ PHP_METHOD(DateTimeZone, __construct) } /* }}} */ -static bool php_date_timezone_initialize_from_hash(php_timezone_obj **tzobj, const HashTable *myht) /* {{{ */ +static bool php_date_timezone_initialize_from_hash(php_timezone_obj *tzobj, const HashTable *myht) /* {{{ */ { - zval *z_timezone_type; + zval *z_timezone_type; + zval *z_timezone; if ((z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type") - 1)) == NULL) { return false; } - zval *z_timezone; - if ((z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone") - 1)) == NULL) { return false; } @@ -4086,7 +4302,7 @@ static bool php_date_timezone_initialize_from_hash(php_timezone_obj **tzobj, con if (UNEXPECTED(zend_str_has_nul_byte(Z_STR_P(z_timezone)))) { return false; } - return timezone_initialize(*tzobj, Z_STR_P(z_timezone), NULL); + return timezone_initialize(tzobj, Z_STR_P(z_timezone), NULL); } /* }}} */ /* {{{ */ @@ -4101,7 +4317,7 @@ PHP_METHOD(DateTimeZone, __set_state) php_date_instantiate(date_ce_timezone, return_value); tzobj = Z_PHPTIMEZONE_P(return_value); - if (!php_date_timezone_initialize_from_hash(&tzobj, myht)) { + if (!php_date_timezone_initialize_from_hash(tzobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object"); RETURN_THROWS(); } @@ -4121,7 +4337,7 @@ PHP_METHOD(DateTimeZone, __wakeup) myht = Z_OBJPROP_P(object); - if (!php_date_timezone_initialize_from_hash(&tzobj, myht)) { + if (!php_date_timezone_initialize_from_hash(tzobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object"); RETURN_THROWS(); } @@ -4185,7 +4401,7 @@ PHP_METHOD(DateTimeZone, __unserialize) tzobj = Z_PHPTIMEZONE_P(object); - if (!php_date_timezone_initialize_from_hash(&tzobj, myht)) { + if (!php_date_timezone_initialize_from_hash(tzobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object"); RETURN_THROWS(); } @@ -4612,10 +4828,10 @@ static void php_date_interval_initialize_from_hash(php_interval_obj *intobj, con { /* If we have a date_string, use that instead */ const zval *date_str = zend_hash_str_find(myht, "date_string", strlen("date_string")); + timelib_time *time; + timelib_error_container *err; if (date_str && Z_TYPE_P(date_str) == IS_STRING) { - timelib_time *time; - timelib_error_container *err = NULL; - + err = NULL; time = timelib_strtotime(Z_STRVAL_P(date_str), Z_STRLEN_P(date_str), &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper); if (err->error_count > 0) { diff --git a/ext/date/php_date.h b/ext/date/php_date.h index 8d7dcd2a5ec1..ffb8072b47c7 100644 --- a/ext/date/php_date.h +++ b/ext/date/php_date.h @@ -161,4 +161,10 @@ PHPAPI bool php_date_initialize(php_date_obj *dateobj, const char *time_str, siz PHPAPI void php_date_initialize_from_ts_long(php_date_obj *dateobj, zend_long sec, int usec); PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts); +#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; +#endif +const zend_opcache_static_cache_safe_direct_handlers *php_date_get_direct_cache_handlers(void); + #endif /* PHP_DATE_H */ diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 3d005b3835a7..ed1496582e90 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -135,6 +135,67 @@ static zend_result (*orig_post_startup_cb)(void); static zend_result accel_post_startup(void); static zend_result accel_finish_startup(void); +static const zend_opcache_static_cache_accelerator_handlers *static_cache_handlers = NULL; + +void zend_accel_register_static_cache_handlers(const zend_opcache_static_cache_accelerator_handlers *handlers) +{ + static_cache_handlers = handlers; +} + +static zend_always_inline void zend_accel_static_cache_startup(void) +{ + if (static_cache_handlers != NULL && static_cache_handlers->startup != NULL) { + static_cache_handlers->startup(); + } +} + +static zend_always_inline void zend_accel_static_cache_post_startup(void) +{ + if (static_cache_handlers != NULL && static_cache_handlers->post_startup != NULL) { + static_cache_handlers->post_startup(); + } +} + +static zend_always_inline zend_result zend_accel_static_cache_rinit(void) +{ + if (static_cache_handlers != NULL && static_cache_handlers->rinit != NULL) { + return static_cache_handlers->rinit(); + } + + return SUCCESS; +} + +static zend_always_inline void zend_accel_static_cache_invalidate_script(zend_persistent_script *persistent_script) +{ + if (static_cache_handlers != NULL && static_cache_handlers->invalidate_script != NULL) { + static_cache_handlers->invalidate_script(persistent_script); + } +} + +#ifdef ZTS +typedef struct _zend_accel_thread_startup_config { + bool enabled; + zend_atomic_bool valid; + zend_accel_directives accel_directives; +} zend_accel_thread_startup_config; + +static zend_accel_thread_startup_config accel_thread_startup_config; + +static zend_always_inline void accel_apply_thread_startup_config(zend_accel_globals *accel_globals) +{ + if (!zend_atomic_bool_load_ex(&accel_thread_startup_config.valid)) { + return; + } + + accel_globals->enabled = accel_thread_startup_config.enabled; + accel_globals->accel_directives = accel_thread_startup_config.accel_directives; +} + +static zend_always_inline void accel_sync_thread_startup_config(void) +{ + accel_apply_thread_startup_config(TSRMG_FAST_BULK(accel_globals_offset, zend_accel_globals *)); +} +#endif #ifndef ZEND_WIN32 # define PRELOAD_SUPPORT @@ -1453,6 +1514,7 @@ zend_result zend_accel_invalidate(zend_string *filename, bool force) if (force || !ZCG(accel_directives).validate_timestamps || do_validate_timestamps(persistent_script, &file_handle) == FAILURE) { + zend_accel_static_cache_invalidate_script(persistent_script); HANDLE_BLOCK_INTERRUPTIONS(); SHM_UNPROTECT(); zend_accel_lock_discard_script(persistent_script); @@ -2149,6 +2211,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) /* If script is found then validate_timestamps if option is enabled */ if (persistent_script && ZCG(accel_directives).validate_timestamps) { if (validate_timestamp_and_record(persistent_script, file_handle) == FAILURE) { + zend_accel_static_cache_invalidate_script(persistent_script); zend_accel_lock_discard_script(persistent_script); persistent_script = NULL; } @@ -2665,6 +2728,10 @@ static void accel_reset_pcre_cache(void) ZEND_RINIT_FUNCTION(zend_accelerator) { +#ifdef ZTS + accel_sync_thread_startup_config(); +#endif + if (!ZCG(enabled) || !accel_startup_ok) { ZCG(accelerator_enabled) = false; return SUCCESS; @@ -2803,6 +2870,8 @@ ZEND_RINIT_FUNCTION(zend_accelerator) } #endif + zend_accel_static_cache_rinit(); + return SUCCESS; } @@ -2967,6 +3036,9 @@ static void accel_globals_ctor(zend_accel_globals *accel_globals) memset(accel_globals, 0, sizeof(zend_accel_globals)); accel_globals->key = zend_string_alloc(ZCG_KEY_LEN, true); GC_MAKE_PERSISTENT_LOCAL(accel_globals->key); +#ifdef ZTS + accel_apply_thread_startup_config(accel_globals); +#endif } static void accel_globals_dtor(zend_accel_globals *accel_globals) @@ -3188,6 +3260,7 @@ static int accel_startup(zend_extension *extension) { #ifdef ZTS accel_globals_id = ts_allocate_fast_id(&accel_globals_id, &accel_globals_offset, sizeof(zend_accel_globals), (ts_allocate_ctor) accel_globals_ctor, (ts_allocate_dtor) accel_globals_dtor); + zend_atomic_bool_store_ex(&accel_thread_startup_config.valid, false); #else accel_globals_ctor(&accel_globals); #endif @@ -3204,6 +3277,12 @@ static int accel_startup(zend_extension *extension) zend_accel_register_ini_entries(); +#ifdef ZTS + accel_thread_startup_config.enabled = ZCG(enabled); + accel_thread_startup_config.accel_directives = ZCG(accel_directives); + zend_atomic_bool_store_ex(&accel_thread_startup_config.valid, true); +#endif + #ifdef ZEND_WIN32 if (UNEXPECTED(accel_gen_uname_id() == FAILURE)) { zps_startup_failure("Unable to get user name", NULL, accelerator_remove_cb); @@ -3462,6 +3541,21 @@ static zend_result accel_post_startup(void) return FAILURE; } + /* Initialize static cache if configured */ + bool static_cache_configured = + ZCG(accel_directives).static_cache_volatile_size_mb != 0 || + ZCG(accel_directives).static_cache_persistent_size_mb != 0 + ; + bool static_cache_preload_configured = static_cache_configured && + ZCG(accel_directives).preload && + *ZCG(accel_directives).preload + ; + + if (static_cache_preload_configured) { + zend_accel_static_cache_startup(); + } + zend_accel_static_cache_post_startup(); + if (ZCG(enabled) && accel_startup_ok) { /* Override inheritance cache callbaks */ accelerator_orig_inheritance_cache_get = zend_inheritance_cache_get; diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 91642e288d31..e08fa53c4f29 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -139,8 +139,19 @@ typedef struct _zend_persistent_script { } dynamic_members; } zend_persistent_script; +typedef struct _zend_opcache_static_cache_accelerator_handlers { + void (*startup)(void); + void (*post_startup)(void); + zend_result (*rinit)(void); + void (*invalidate_script)(zend_persistent_script *persistent_script); +} zend_opcache_static_cache_accelerator_handlers; + +void zend_accel_register_static_cache_handlers(const zend_opcache_static_cache_accelerator_handlers *handlers); + typedef struct _zend_accel_directives { zend_long memory_consumption; + zend_long static_cache_volatile_size_mb; + zend_long static_cache_persistent_size_mb; zend_long max_accelerated_files; double max_wasted_percentage; char *user_blacklist_filename; diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index 70138726c56e..a3f8de655cea 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -331,6 +331,11 @@ PHP_NEW_EXTENSION([opcache], m4_normalize([ zend_accelerator_hash.c zend_accelerator_module.c zend_accelerator_util_funcs.c + zend_static_cache.c + zend_static_cache_storage.c + zend_static_cache_shared_graph.c + zend_static_cache_entries.c + zend_static_cache_statics.c zend_file_cache.c zend_persist_calc.c zend_persist.c diff --git a/ext/opcache/config.w32 b/ext/opcache/config.w32 index 397fa1bdd87d..55e3bc9f3e5e 100644 --- a/ext/opcache/config.w32 +++ b/ext/opcache/config.w32 @@ -10,6 +10,11 @@ ZEND_EXTENSION('opcache', "\ zend_accelerator_hash.c \ zend_accelerator_module.c \ zend_accelerator_util_funcs.c \ + zend_static_cache.c \ + zend_static_cache_storage.c \ + zend_static_cache_shared_graph.c \ + zend_static_cache_entries.c \ + zend_static_cache_statics.c \ zend_persist.c \ zend_persist_calc.c \ zend_file_cache.c \ diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 64a48068f378..dbd03c4dfa39 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -3413,6 +3413,15 @@ static void ZEND_FASTCALL zend_jit_uninit_static_prop(void) zend_get_unmangled_property_name(property_info->name)); } +static void ZEND_FASTCALL zend_jit_static_prop_access_helper(const zend_property_info *property_info) +{ + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_class_static_access_hook != NULL) + ) { + zend_class_static_access_hook(property_info->ce); + } +} + static void ZEND_FASTCALL zend_jit_free_trampoline_helper(zend_function *func) { ZEND_ASSERT(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index cf43d3ad840f..0d4b35bc5646 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -16103,6 +16103,17 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, if_cached = ir_IF(ref); ir_IF_TRUE(if_cached); + /* Keep the JIT static-property fast path in sync with + * zend_fetch_static_property_address(): OPcache Static Cache may need to + * refresh class-blob state even when the run-time cache already resolved + * the static property slot. */ + prop_info_ref = known_prop_info != NULL + ? ir_CONST_ADDR(known_prop_info) + : ir_LOAD_L(ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2)) + ; + ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_static_prop_access_helper), prop_info_ref); + zend_jit_check_exception_undef_result(jit, opline); + if (fetch_type == BP_VAR_R || fetch_type == BP_VAR_RW) { if (!known_prop_info || ZEND_TYPE_IS_SET(known_prop_info->type)) { ir_ref merge = IR_UNUSED; @@ -16113,8 +16124,6 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, ir_IF_FALSE_cold(if_def); if (!known_prop_info) { // JIT: if (ZEND_TYPE_IS_SET(property_info->type)) - prop_info_ref = ir_LOAD_L( - ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2)); if_typed = ir_IF(ir_AND_U32( ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))), ir_CONST_U32(_ZEND_TYPE_MASK))); @@ -16138,20 +16147,16 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, } else if (fetch_type == BP_VAR_W) { flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS; if (flags && (!known_prop_info || ZEND_TYPE_IS_SET(known_prop_info->type))) { - ir_ref merge = IR_UNUSED; + ir_ref merge = IR_UNUSED; if (!known_prop_info) { // JIT: if (ZEND_TYPE_IS_SET(property_info->type)) - prop_info_ref = ir_LOAD_L( - ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2)); if_typed = ir_IF(ir_AND_U32( ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))), ir_CONST_U32(_ZEND_TYPE_MASK))); ir_IF_FALSE(if_typed); ir_END_list(merge); ir_IF_TRUE(if_typed); - } else { - prop_info_ref = ir_CONST_ADDR(known_prop_info); } // JIT: zend_handle_fetch_obj_flags(NULL, *retval, NULL, property_info, flags); diff --git a/ext/opcache/opcache.stub.php b/ext/opcache/opcache.stub.php index 32673bb1dcee..4b793b491b8f 100644 --- a/ext/opcache/opcache.stub.php +++ b/ext/opcache/opcache.stub.php @@ -2,6 +2,8 @@ /** @generate-class-entries */ +namespace { + function opcache_reset(): bool {} /** @@ -25,3 +27,95 @@ function opcache_get_configuration(): array|false {} function opcache_is_script_cached(string $filename): bool {} function opcache_is_script_cached_in_file_cache(string $filename): bool {} + +} + +namespace OPcache { + +class StaticCacheException extends \Exception +{ +} + +#[\Attribute(13)] /* TARGET_CLASS | TARGET_METHOD | TARGET_PROPERTY */ +final class PersistentStatic +{ +} + +enum CacheStrategy: int +{ + case Immediate = 0; + case Tracking = 1; +} + +#[\Attribute(13)] /* TARGET_CLASS | TARGET_METHOD | TARGET_PROPERTY */ +final class VolatileStatic +{ + public readonly int $ttl; + + public readonly CacheStrategy $strategy; + + public function __construct(int $ttl = 0, CacheStrategy $strategy = CacheStrategy::Immediate) {} +} + +#[\Attribute(1)] /* TARGET_CLASS */ +final class __DirectCacheSafe +{ +} + +function volatile_store(string $key, null|bool|int|float|string|array|object $value, int $ttl = 0): bool {} + +function volatile_store_array(array $values, int $ttl = 0): bool {} + +function volatile_fetch(string $key, null|bool|int|float|string|array|object $default = null): null|bool|int|float|string|array|object {} + +/** + * @return array|null + */ +function volatile_fetch_array(array $keys, ?array $default = null): ?array {} + +function volatile_exists(string $key): bool {} + +function volatile_lock(string $key): bool {} + +function volatile_delete(string $key): void {} + +function volatile_delete_array(array $keys): void {} + +function volatile_clear(): void {} + +/** + * @return array + */ +function volatile_cache_info(): array {} + +function persistent_store(string $key, null|bool|int|float|string|array|object $value): void {} + +function persistent_store_array(array $values): void {} + +function persistent_fetch(string $key, null|bool|int|float|string|array|object $default = null): null|bool|int|float|string|array|object {} + +/** + * @return array|null + */ +function persistent_fetch_array(array $keys, ?array $default = null): ?array {} + +function persistent_exists(string $key): bool {} + +function persistent_lock(string $key): bool {} + +function persistent_delete(string $key): void {} + +function persistent_delete_array(array $keys): void {} + +function persistent_clear(): void {} + +function persistent_atomic_increment(string $key, int $step = 1): int {} + +function persistent_atomic_decrement(string $key, int $step = 1): int {} + +/** + * @return array + */ +function persistent_cache_info(): array {} + +} diff --git a/ext/opcache/opcache_arginfo.h b/ext/opcache/opcache_arginfo.h index 60a1633154c9..4aa7f5323e33 100644 --- a/ext/opcache/opcache_arginfo.h +++ b/ext/opcache/opcache_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit opcache.stub.php instead. - * Stub hash: a8de025fa96a78db3a26d53a18bb2b365d094eca */ + * Stub hash: 7014b50c5940d238d5a5b3e4902a457f183ee298 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_reset, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -28,6 +28,84 @@ ZEND_END_ARG_INFO() #define arginfo_opcache_is_script_cached_in_file_cache arginfo_opcache_compile_file +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_store, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_MASK(0, value, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, ttl, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_store_array, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, ttl, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_OPcache_volatile_fetch, 0, 1, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_MASK(0, default, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_fetch_array, 0, 1, IS_ARRAY, 1) + ZEND_ARG_TYPE_INFO(0, keys, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, default, IS_ARRAY, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_exists, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_OPcache_volatile_lock arginfo_OPcache_volatile_exists + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_delete, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_delete_array, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, keys, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_clear, 0, 0, IS_VOID, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_cache_info, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_persistent_store, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_MASK(0, value, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_persistent_store_array, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +#define arginfo_OPcache_persistent_fetch arginfo_OPcache_volatile_fetch + +#define arginfo_OPcache_persistent_fetch_array arginfo_OPcache_volatile_fetch_array + +#define arginfo_OPcache_persistent_exists arginfo_OPcache_volatile_exists + +#define arginfo_OPcache_persistent_lock arginfo_OPcache_volatile_exists + +#define arginfo_OPcache_persistent_delete arginfo_OPcache_volatile_delete + +#define arginfo_OPcache_persistent_delete_array arginfo_OPcache_volatile_delete_array + +#define arginfo_OPcache_persistent_clear arginfo_OPcache_volatile_clear + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_persistent_atomic_increment, 0, 1, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, step, IS_LONG, 0, "1") +ZEND_END_ARG_INFO() + +#define arginfo_OPcache_persistent_atomic_decrement arginfo_OPcache_persistent_atomic_increment + +#define arginfo_OPcache_persistent_cache_info arginfo_OPcache_volatile_cache_info + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_OPcache_VolatileStatic___construct, 0, 0, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, ttl, IS_LONG, 0, "0") + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, strategy, OPcache\\CacheStrategy, 0, "OPcache\\CacheStrategy::Immediate") +ZEND_END_ARG_INFO() + ZEND_FUNCTION(opcache_reset); ZEND_FUNCTION(opcache_get_status); ZEND_FUNCTION(opcache_compile_file); @@ -36,6 +114,29 @@ ZEND_FUNCTION(opcache_jit_blacklist); ZEND_FUNCTION(opcache_get_configuration); ZEND_FUNCTION(opcache_is_script_cached); ZEND_FUNCTION(opcache_is_script_cached_in_file_cache); +ZEND_FUNCTION(OPcache_volatile_store); +ZEND_FUNCTION(OPcache_volatile_store_array); +ZEND_FUNCTION(OPcache_volatile_fetch); +ZEND_FUNCTION(OPcache_volatile_fetch_array); +ZEND_FUNCTION(OPcache_volatile_exists); +ZEND_FUNCTION(OPcache_volatile_lock); +ZEND_FUNCTION(OPcache_volatile_delete); +ZEND_FUNCTION(OPcache_volatile_delete_array); +ZEND_FUNCTION(OPcache_volatile_clear); +ZEND_FUNCTION(OPcache_volatile_cache_info); +ZEND_FUNCTION(OPcache_persistent_store); +ZEND_FUNCTION(OPcache_persistent_store_array); +ZEND_FUNCTION(OPcache_persistent_fetch); +ZEND_FUNCTION(OPcache_persistent_fetch_array); +ZEND_FUNCTION(OPcache_persistent_exists); +ZEND_FUNCTION(OPcache_persistent_lock); +ZEND_FUNCTION(OPcache_persistent_delete); +ZEND_FUNCTION(OPcache_persistent_delete_array); +ZEND_FUNCTION(OPcache_persistent_clear); +ZEND_FUNCTION(OPcache_persistent_atomic_increment); +ZEND_FUNCTION(OPcache_persistent_atomic_decrement); +ZEND_FUNCTION(OPcache_persistent_cache_info); +ZEND_METHOD(OPcache_VolatileStatic, __construct); static const zend_function_entry ext_functions[] = { ZEND_FE(opcache_reset, arginfo_opcache_reset) @@ -46,5 +147,115 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(opcache_get_configuration, arginfo_opcache_get_configuration) ZEND_FE(opcache_is_script_cached, arginfo_opcache_is_script_cached) ZEND_FE(opcache_is_script_cached_in_file_cache, arginfo_opcache_is_script_cached_in_file_cache) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_store"), zif_OPcache_volatile_store, arginfo_OPcache_volatile_store, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_store_array"), zif_OPcache_volatile_store_array, arginfo_OPcache_volatile_store_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_fetch"), zif_OPcache_volatile_fetch, arginfo_OPcache_volatile_fetch, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_fetch_array"), zif_OPcache_volatile_fetch_array, arginfo_OPcache_volatile_fetch_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_exists"), zif_OPcache_volatile_exists, arginfo_OPcache_volatile_exists, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_lock"), zif_OPcache_volatile_lock, arginfo_OPcache_volatile_lock, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_delete"), zif_OPcache_volatile_delete, arginfo_OPcache_volatile_delete, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_delete_array"), zif_OPcache_volatile_delete_array, arginfo_OPcache_volatile_delete_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_clear"), zif_OPcache_volatile_clear, arginfo_OPcache_volatile_clear, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_cache_info"), zif_OPcache_volatile_cache_info, arginfo_OPcache_volatile_cache_info, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_store"), zif_OPcache_persistent_store, arginfo_OPcache_persistent_store, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_store_array"), zif_OPcache_persistent_store_array, arginfo_OPcache_persistent_store_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_fetch"), zif_OPcache_persistent_fetch, arginfo_OPcache_persistent_fetch, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_fetch_array"), zif_OPcache_persistent_fetch_array, arginfo_OPcache_persistent_fetch_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_exists"), zif_OPcache_persistent_exists, arginfo_OPcache_persistent_exists, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_lock"), zif_OPcache_persistent_lock, arginfo_OPcache_persistent_lock, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_delete"), zif_OPcache_persistent_delete, arginfo_OPcache_persistent_delete, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_delete_array"), zif_OPcache_persistent_delete_array, arginfo_OPcache_persistent_delete_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_clear"), zif_OPcache_persistent_clear, arginfo_OPcache_persistent_clear, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_atomic_increment"), zif_OPcache_persistent_atomic_increment, arginfo_OPcache_persistent_atomic_increment, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_atomic_decrement"), zif_OPcache_persistent_atomic_decrement, arginfo_OPcache_persistent_atomic_decrement, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_cache_info"), zif_OPcache_persistent_cache_info, arginfo_OPcache_persistent_cache_info, 0, NULL, NULL) ZEND_FE_END }; + +static const zend_function_entry class_OPcache_VolatileStatic_methods[] = { + ZEND_ME(OPcache_VolatileStatic, __construct, arginfo_class_OPcache_VolatileStatic___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static zend_class_entry *register_class_OPcache_StaticCacheException(zend_class_entry *class_entry_Exception) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "OPcache", "StaticCacheException", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Exception, 0); + + return class_entry; +} + +static zend_class_entry *register_class_OPcache_PersistentStatic(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "OPcache", "PersistentStatic", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zend_string *attribute_name_Attribute_class_OPcache_PersistentStatic_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); + zend_attribute *attribute_Attribute_class_OPcache_PersistentStatic_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_OPcache_PersistentStatic_0, 1); + zend_string_release_ex(attribute_name_Attribute_class_OPcache_PersistentStatic_0, true); + ZVAL_LONG(&attribute_Attribute_class_OPcache_PersistentStatic_0->args[0].value, 13); + + return class_entry; +} + +static zend_class_entry *register_class_OPcache_CacheStrategy(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum("OPcache\\CacheStrategy", IS_LONG, NULL); + + zval enum_case_Immediate_value; + ZVAL_LONG(&enum_case_Immediate_value, 0); + zend_enum_add_case_cstr(class_entry, "Immediate", &enum_case_Immediate_value); + + zval enum_case_Tracking_value; + ZVAL_LONG(&enum_case_Tracking_value, 1); + zend_enum_add_case_cstr(class_entry, "Tracking", &enum_case_Tracking_value); + + return class_entry; +} + +static zend_class_entry *register_class_OPcache_VolatileStatic(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "OPcache", "VolatileStatic", class_OPcache_VolatileStatic_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zval property_ttl_default_value; + ZVAL_UNDEF(&property_ttl_default_value); + zend_string *property_ttl_name = zend_string_init("ttl", sizeof("ttl") - 1, true); + zend_declare_typed_property(class_entry, property_ttl_name, &property_ttl_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(property_ttl_name, true); + + zval property_strategy_default_value; + ZVAL_UNDEF(&property_strategy_default_value); + zend_string *property_strategy_name = zend_string_init("strategy", sizeof("strategy") - 1, true); + zend_string *property_strategy_class_OPcache_CacheStrategy = zend_string_init("OPcache\\CacheStrategy", sizeof("OPcache\\CacheStrategy")-1, 1); + zend_declare_typed_property(class_entry, property_strategy_name, &property_strategy_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_strategy_class_OPcache_CacheStrategy, 0, 0)); + zend_string_release_ex(property_strategy_name, true); + + zend_string *attribute_name_Attribute_class_OPcache_VolatileStatic_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); + zend_attribute *attribute_Attribute_class_OPcache_VolatileStatic_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_OPcache_VolatileStatic_0, 1); + zend_string_release_ex(attribute_name_Attribute_class_OPcache_VolatileStatic_0, true); + ZVAL_LONG(&attribute_Attribute_class_OPcache_VolatileStatic_0->args[0].value, 13); + + return class_entry; +} + +static zend_class_entry *register_class_OPcache___DirectCacheSafe(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "OPcache", "__DirectCacheSafe", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zend_string *attribute_name_Attribute_class_OPcache___DirectCacheSafe_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); + zend_attribute *attribute_Attribute_class_OPcache___DirectCacheSafe_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_OPcache___DirectCacheSafe_0, 1); + zend_string_release_ex(attribute_name_Attribute_class_OPcache___DirectCacheSafe_0, true); + ZVAL_LONG(&attribute_Attribute_class_OPcache___DirectCacheSafe_0->args[0].value, 1); + + return class_entry; +} diff --git a/ext/opcache/tests/fpm/skipif.inc b/ext/opcache/tests/fpm/skipif.inc new file mode 100644 index 000000000000..bc6813f63979 --- /dev/null +++ b/ext/opcache/tests/fpm/skipif.inc @@ -0,0 +1,13 @@ + +--FILE-- +start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.file_update_protection' => '0', + 'opcache.jit' => 'tracing', + 'opcache.jit_buffer_size' => '64M', + 'opcache.jit_hot_loop' => '0', + 'opcache.jit_hot_func' => '2', + 'opcache.jit_hot_return' => '0', + 'opcache.jit_hot_side_exit' => '1', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'request=1')->expectBody('1,7,6,21,1'); +$tester->request(query: 'request=2')->expectBody("1,6,21,1,1\n11,7,33,1"); +$tester->request(query: 'request=3')->expectBody('1,7,33,1,1'); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt new file mode 100644 index 000000000000..563e39b2bbc5 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt @@ -0,0 +1,75 @@ +--TEST-- +FPM: OPcache PersistentStatic class blob persists across requests +--SKIPIF-- + +--FILE-- +start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.persistent_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'request=1')->expectBody('1,1'); +$tester->request(query: 'request=2')->expectBody("1,10,2,1\n2,21,3"); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_complex_value_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_complex_value_persistence_001.phpt new file mode 100644 index 000000000000..90c821c5e5a3 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_complex_value_persistence_001.phpt @@ -0,0 +1,86 @@ +--TEST-- +FPM: OPcache PersistentStatic persists complex values across requests +--SKIPIF-- + +--FILE-- + new CounterBox(100), 'gap' => []]; + $state['gap'][4] = 'seed'; + unset($state['gap'][4]); + } + + $state['box']->value++; + $state['gap'][] = 'tail'; + + return $state['box']->value . ':' . array_key_last($state['gap']); + } +} + +if (ComplexState::$box === null) { + ComplexState::$box = new CounterBox(1); + ComplexState::$gap[3] = 'seed'; + unset(ComplexState::$gap[3]); +} + +ComplexState::$box->value++; +ComplexState::$gap[] = 'tail'; + +echo ComplexState::$box->value, ',', array_key_last(ComplexState::$gap), ',', ComplexState::methodState(), "\n"; +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.persistent_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request()->expectBody('2,4,101:5'); +$tester->request()->expectBody('3,5,102:6'); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt new file mode 100644 index 000000000000..d7dda3838147 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt @@ -0,0 +1,67 @@ +--TEST-- +FPM: OPcache PersistentStatic snapshots object assignments without following object property writes +--SKIPIF-- + +--FILE-- + 1]; +echo NestedObjectPropertyState::$propertyState->count, "\n"; +echo OPcache\persistent_cache_info()['entry_count'], "\n"; + +NestedObjectPropertyState::$propertyState->count++; +echo NestedObjectPropertyState::$propertyState->count, "\n"; +echo OPcache\persistent_cache_info()['entry_count'], "\n"; + +NestedObjectPropertyState::$propertyState->count = 10; +echo NestedObjectPropertyState::$propertyState->count, "\n"; +echo OPcache\persistent_cache_info()['entry_count']; +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.optimization_level' => '0', + 'opcache.file_update_protection' => '0', + 'opcache.jit' => '0', +]); +$tester->expectLogStartNotices(); + +$tester->request()->expectBody("1\n1\n2\n1\n10\n1"); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_persistence_001.phpt new file mode 100644 index 000000000000..512999522da6 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_persistence_001.phpt @@ -0,0 +1,80 @@ +--TEST-- +FPM: OPcache PersistentStatic persists across requests +--SKIPIF-- + +--FILE-- +start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.persistent_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request()->expectBody('1,1,1,1,1'); +$tester->request()->expectBody('2,2,2,1,1'); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt new file mode 100644 index 000000000000..804dc30b2d28 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt @@ -0,0 +1,63 @@ +--TEST-- +FPM: OPcache PersistentStatic publishes nested array writes for property-scoped state +--SKIPIF-- + +--FILE-- + [1]]; +echo json_encode(NestedArrayPropertyState::$propertyState['numbers']), "\n"; +echo OPcache\persistent_cache_info()['entry_count'], "\n"; + +NestedArrayPropertyState::$propertyState['numbers'][] = 2; +echo json_encode(NestedArrayPropertyState::$propertyState['numbers']), "\n"; +echo OPcache\persistent_cache_info()['entry_count']; +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.optimization_level' => '0', + 'opcache.file_update_protection' => '0', + 'opcache.jit' => '0', +]); +$tester->expectLogStartNotices(); + +$tester->request()->expectBody("[1]\n1\n[1,2]\n1"); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_scope_writes_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_scope_writes_001.phpt new file mode 100644 index 000000000000..a1138952a85c --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_scope_writes_001.phpt @@ -0,0 +1,122 @@ +--TEST-- +FPM: OPcache PersistentStatic class, property, and method state handles sequential writes by scope +--SKIPIF-- + +--FILE-- + 0, 'numbers' => []]; + + public static function next(): int + { + static $value = 0; + + return ++$value; + } +} + +class FpmRacePropertyState +{ + #[OPcache\PersistentStatic] + public static array $data = ['value' => 0, 'numbers' => []]; +} + +class FpmRaceMethodState +{ + #[OPcache\PersistentStatic] + public static function next(): int + { + static $value = 0; + + return ++$value; + } +} + +$action = $_GET['action'] ?? 'reset'; + +if ($action === 'reset') { + opcache_reset(); + echo 'reset'; + return; +} + +if ($action === 'seed') { + FpmRaceClassState::$data = ['value' => 1, 'numbers' => [1]]; + FpmRaceClassState::next(); + FpmRacePropertyState::$data = ['value' => 1, 'numbers' => [1]]; + FpmRaceMethodState::next(); + echo 'seed'; + return; +} + +$tuple = static function (): string { + return FpmRaceClassState::$data['value'] . ',' + . array_sum(FpmRaceClassState::$data['numbers']) . ',' + . FpmRaceClassState::next() . ',' + . FpmRacePropertyState::$data['value'] . ',' + . array_sum(FpmRacePropertyState::$data['numbers']) . ',' + . FpmRaceMethodState::next(); +}; + +if ($action === 'writer') { + $classMethod = FpmRaceClassState::next(); + FpmRaceClassState::$data['value'] = 10; + FpmRaceClassState::$data['numbers'][] = 20; + FpmRacePropertyState::$data['value'] = 10; + FpmRacePropertyState::$data['numbers'][] = 20; + $method = FpmRaceMethodState::next(); + echo 'writer:', FpmRaceClassState::$data['value'], ',', array_sum(FpmRaceClassState::$data['numbers']), ',', $classMethod, ',', FpmRacePropertyState::$data['value'], ',', array_sum(FpmRacePropertyState::$data['numbers']), ',', $method; + return; +} + +if ($action === 'verify') { + echo 'verify:', $tuple(); + return; +} +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.file_update_protection' => '0', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'action=reset')->expectBody('reset'); +$tester->request(query: 'action=seed')->expectBody('seed'); +$tester->request(query: 'action=writer')->expectBody('writer:10,21,2,10,21,2'); +$tester->request(query: 'action=verify')->expectBody('verify:10,21,3,10,21,3'); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_complex_value_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_complex_value_persistence_001.phpt new file mode 100644 index 000000000000..e932f455aa25 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_complex_value_persistence_001.phpt @@ -0,0 +1,135 @@ +--TEST-- +FPM: OPcache volatile cache persists complex values across requests +--SKIPIF-- + +--FILE-- +id = $id; + $this->name = $name; + } + + public function __serialize(): array + { + return ['id' => $this->id, 'name' => $this->name]; + } + + public function __unserialize(array $data): void + { + $this->id = $data['id']; + $this->name = $data['name']; + } + + public function info(): string + { + return $this->id . ':' . $this->name; + } +} + +$action = $_GET['action'] ?? 'seed'; + +if ($action === 'seed') { + $gap = []; + $gap[4] = 'seed'; + unset($gap[4]); + + $payload = [ + 'props' => new SimpleUser('Alice', 30), + 'serialize' => new SerUser(7, 'Bob'), + 'internal' => new DateTimeImmutable('2026-06-15 09:30:00', new DateTimeZone('UTC')), + 'gap' => $gap, + ]; + + $shared = new stdClass(); + $shared->value = 42; + + $refs = ['value' => 1]; + $refs['alias'] =& $refs['value']; + + OPcache\volatile_clear(); + var_dump(OPcache\volatile_store('complex', $payload)); + var_dump(OPcache\volatile_store('shared_pair', [$shared, $shared])); + var_dump(OPcache\volatile_store('refs', $refs)); + echo "seed\n"; + return; +} + +$complex = OPcache\volatile_fetch('complex'); +$complex['gap'][] = 'tail'; +echo $complex['props']->name, ',', $complex['props']->age, ',', $complex['serialize']->info(), ',', $complex['internal']->format('Y-m-d H:i:s'), ',', array_key_last($complex['gap']), "\n"; + +$pair = OPcache\volatile_fetch('shared_pair'); +var_dump(spl_object_id($pair[0]) === spl_object_id($pair[1])); + +$refs = OPcache\volatile_fetch('refs'); +$refs['alias'] = 7; +var_dump($refs['value']); + +echo "fetch\n"; +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.volatile_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'action=seed')->expectBody( + "bool(true)\n" . + "bool(true)\n" . + "bool(true)\n" . + "seed" +); + +$tester->request(query: 'action=fetch')->expectBody( + "Alice,30,7:Bob,2026-06-15 09:30:00,5\n" . + "bool(true)\n" . + "int(7)\n" . + "fetch" +); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_direct_cache_safe_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_direct_cache_safe_001.phpt new file mode 100644 index 000000000000..424e6cdfc7d1 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_direct_cache_safe_001.phpt @@ -0,0 +1,194 @@ +--TEST-- +FPM: OPcache __DirectCacheSafe subclasses survive requests, safe serializer overrides stay direct, and wakeup hooks fallback +--EXTENSIONS-- +opcache +spl +--SKIPIF-- + +--FILE-- +label = $label; + $this->revision = $revision; + } + + public function describe(): string + { + return $this->label . ':' . $this->revision; + } +} + +class LabelIterator extends ArrayIterator +{ +} + +class TaggedCollection extends ArrayObject +{ + private string $type; + + public function __construct(array $data, string $type, string $iteratorClass) + { + parent::__construct($data, 0, $iteratorClass); + $this->type = $type; + } + + public function type(): string + { + return $this->type; + } +} + +class CustomSerializedDateTime extends DateTime +{ + public static int $unserializeCount = 0; + + private string $label; + + public function __construct(string $time, DateTimeZone $timezone, string $label) + { + parent::__construct($time, $timezone); + $this->label = $label; + } + + public function __serialize(): array + { + return parent::__serialize() + ['label' => $this->label]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCount++; + parent::__unserialize($data); + $this->label = $data['label']; + } + + public function label(): string + { + return $this->label; + } +} + +class WakefulSerializedDateTime extends DateTime +{ + public static int $unserializeCount = 0; + + private string $label; + + public function __construct(string $time, DateTimeZone $timezone, string $label) + { + parent::__construct($time, $timezone); + $this->label = $label; + } + + public function __serialize(): array + { + return parent::__serialize() + ['label' => $this->label]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCount++; + parent::__unserialize($data); + $this->label = $data['label']; + } + + public function __sleep(): array + { + return ['label']; + } + + public function label(): string + { + return $this->label; + } +} + +$action = $_GET['action'] ?? 'seed'; + +if ($action === 'seed') { + OPcache\volatile_clear(); + + $event = new EventDateTime('2026-06-15 09:30:00.123456', new DateTimeZone('Europe/Paris'), 'launch', 7); + $collection = new TaggedCollection(['alpha' => 10, 'beta' => 20], 'metric', LabelIterator::class); + $fallback = new CustomSerializedDateTime('2026-06-15 10:45:00.654321', new DateTimeZone('UTC'), 'fallback'); + $wakeful = new WakefulSerializedDateTime('2026-06-15 12:15:00.987654', new DateTimeZone('UTC'), 'fallback'); + + var_dump(OPcache\volatile_store('safe_direct_event_datetime', $event)); + var_dump(OPcache\volatile_store('safe_direct_tagged_collection', $collection)); + var_dump(OPcache\volatile_store('safe_direct_custom_datetime', $fallback)); + var_dump(OPcache\volatile_store('safe_direct_wakeful_datetime', $wakeful)); + echo "seed\n"; + return; +} + +$event = OPcache\volatile_fetch('safe_direct_event_datetime'); +$collection = OPcache\volatile_fetch('safe_direct_tagged_collection'); +$fallback = OPcache\volatile_fetch('safe_direct_custom_datetime'); +$wakeful = OPcache\volatile_fetch('safe_direct_wakeful_datetime'); +$iterator = $collection->getIterator(); + +echo $event->format('Y-m-d H:i:s.u e'), ',', $event->describe(), "\n"; +echo $collection->type(), ',', ($iterator instanceof LabelIterator ? 'LabelIterator' : get_debug_type($iterator)), ',', $collection['alpha'], ',', $collection['beta'], "\n"; +echo $fallback->format('Y-m-d H:i:s.u e'), ',', $fallback->label(), ',', CustomSerializedDateTime::$unserializeCount, "\n"; +echo $wakeful->format('Y-m-d H:i:s.u e'), ',', $wakeful->label(), ',', WakefulSerializedDateTime::$unserializeCount, "\n"; +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.volatile_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'action=seed')->expectBody( + "bool(true)\n" . + "bool(true)\n" . + "bool(true)\n" . + "bool(true)\n" . + "seed" +); + +$tester->request(query: 'action=fetch')->expectBody( + "2026-06-15 09:30:00.123456 Europe/Paris,launch:7\n" . + "metric,LabelIterator,10,20\n" . + "2026-06-15 10:45:00.654321 UTC,fallback,0\n" . + "2026-06-15 12:15:00.987654 UTC,fallback,1" +); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_lookup_cache_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_lookup_cache_001.phpt new file mode 100644 index 000000000000..81e6e45b1236 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_lookup_cache_001.phpt @@ -0,0 +1,91 @@ +--TEST-- +FPM: OPcache volatile cache request-local lookup cache sees same-request updates +--SKIPIF-- + +--FILE-- +start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.volatile_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'scenario=hit_store')->expectBody("old\nnew"); +$tester->request(query: 'scenario=miss_store')->expectBody("MISS\ncreated"); +$tester->request(query: 'scenario=hit_delete')->expectBody("old\nMISS"); +$tester->request(query: 'scenario=hit_clear')->expectBody("old\nMISS"); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_nested_shared_object_copy_on_write_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_nested_shared_object_copy_on_write_001.phpt new file mode 100644 index 000000000000..38aaee74e677 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_nested_shared_object_copy_on_write_001.phpt @@ -0,0 +1,110 @@ +--TEST-- +FPM: OPcache volatile cache keeps nested userland objects shared until mutation with __DirectCacheSafe properties across requests +--SKIPIF-- + +--FILE-- + $i, + 'text' => str_repeat(chr(65 + ($i % 26)), 96), + 'flags' => [$i, $i + 1, $i + 2, $i + 3], + ]; + } + + return $rows; +} + +$action = $_GET['action'] ?? 'seed'; + +if ($action === 'seed') { + OPcache\volatile_clear(); + $payload = new WrappedPayload( + new LargePayload(build_rows(), 'nested'), + new DateTimeImmutable('2026-06-15 09:30:00', new DateTimeZone('UTC')), + ); + var_dump(OPcache\volatile_store('materialization_nested_payload', $payload)); + echo "seed\n"; + return; +} + +$before = memory_get_usage(); +$fetched = OPcache\volatile_fetch('materialization_nested_payload'); +$readOnly = $fetched->timestamp->format(DateTimeInterface::ATOM) . '|' . $fetched->payload->rows[100]['text']; +$afterFetch = memory_get_usage(); +$fetched->payload->rows[100]['text'] = 'changed'; +$afterMutate = memory_get_usage(); +$usesZendAlloc = getenv('USE_ZEND_ALLOC') !== '0'; + +var_dump(!$usesZendAlloc || ($afterFetch - $before) < 262144); +var_dump($fetched->payload->rows[100]['text'] === 'changed' && (!$usesZendAlloc || ($afterMutate - $afterFetch) > 131072)); +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.volatile_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'action=seed')->expectBody( + "bool(true)\n" . + "seed" +); + +$tester->request(query: 'action=fetch')->expectBody( + "bool(true)\n" . + "bool(true)" +); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_persistence_001.phpt new file mode 100644 index 000000000000..f8d3e3f6d6fc --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_persistence_001.phpt @@ -0,0 +1,103 @@ +--TEST-- +FPM: OPcache volatile cache persists across requests +--SKIPIF-- + +--FILE-- + 'hello from fpm', + 'payload' => ['a' => 1, 'b' => [2, 3]], + ])); + var_dump(OPcache\volatile_store('ttl_key', 'short-lived', 1)); + echo "seed\n"; + return; +} + +if ($action === 'fetch') { + var_dump(OPcache\volatile_fetch('counter')); + var_dump(OPcache\volatile_fetch('message')); + var_dump(OPcache\volatile_fetch('payload')); + OPcache\volatile_delete_array(['message']); + echo OPcache\volatile_fetch('message', 'MISS'), "\n"; + echo "fetch\n"; + return; +} + +echo OPcache\volatile_fetch('ttl_key', 'MISS'), "\n"; +echo "expire\n"; +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.volatile_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'action=seed')->expectBody( + "bool(true)\n" . + "bool(true)\n" . + "bool(true)\n" . + "seed" +); + +$tester->request(query: 'action=fetch')->expectBody( + "int(41)\n" . + "string(14) \"hello from fpm\"\n" . + "array(2) {\n" . + " [\"a\"]=>\n" . + " int(1)\n" . + " [\"b\"]=>\n" . + " array(2) {\n" . + " [0]=>\n" . + " int(2)\n" . + " [1]=>\n" . + " int(3)\n" . + " }\n" . + "}\n" . + "MISS\n" . + "fetch" +); + +sleep(2); + +$tester->request(query: 'action=expire')->expectBody( + "MISS\n" . + "expire" +); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_shared_object_copy_on_write_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_shared_object_copy_on_write_001.phpt new file mode 100644 index 000000000000..dcb97fcf8e1f --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_shared_object_copy_on_write_001.phpt @@ -0,0 +1,98 @@ +--TEST-- +FPM: OPcache volatile cache keeps plain userland objects shared until mutation across requests +--SKIPIF-- + +--FILE-- + $i, + 'text' => str_repeat(chr(65 + ($i % 26)), 96), + 'flags' => [$i, $i + 1, $i + 2, $i + 3], + ]; + } + + return $rows; +} + +$action = $_GET['action'] ?? 'seed'; + +if ($action === 'seed') { + OPcache\volatile_clear(); + var_dump(OPcache\volatile_store('materialization_plain_payload', new LargePayload(build_rows(), 'plain'))); + echo "seed\n"; + return; +} + +$before = memory_get_usage(); +$fetched = OPcache\volatile_fetch('materialization_plain_payload'); +$readOnly = $fetched->rows[100]['text']; +$afterFetch = memory_get_usage(); +$fetched->rows[100]['text'] = 'changed'; +$afterMutate = memory_get_usage(); +$usesZendAlloc = getenv('USE_ZEND_ALLOC') !== '0'; + +var_dump(!$usesZendAlloc || ($afterFetch - $before) < 262144); +var_dump($fetched->rows[100]['text'] === 'changed' && (!$usesZendAlloc || ($afterMutate - $afterFetch) > 131072)); +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.volatile_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$tester->request(query: 'action=seed')->expectBody( + "bool(true)\n" . + "seed" +); + +$tester->request(query: 'action=fetch')->expectBody( + "bool(true)\n" . + "bool(true)" +); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_persistent_cache_shared_workers_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_persistent_cache_shared_workers_001.phpt new file mode 100644 index 000000000000..8581e32fc4ea --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_persistent_cache_shared_workers_001.phpt @@ -0,0 +1,133 @@ +--TEST-- +FPM: OPcache volatile and persistent caches are shared across static workers +--SKIPIF-- + +--FILE-- +multiRequest(array_fill(0, 4, [ + 'query' => 'action=' . $action . '&seed_pid=' . $seedPid, + ]), readTimeout: 7000); + + $checked = 0; + foreach ($responses as $response) { + $body = trim((string) $response->getBody()); + if (preg_match('/^same:\d+$/', $body)) { + continue; + } + if (!preg_match('/^fetch:(\d+):(.*)$/', $body, $matches)) { + throw new RuntimeException('Unexpected response for ' . $action . ': ' . var_export($body, true)); + } + if ((int) $matches[1] === $seedPid) { + throw new RuntimeException('Seed worker unexpectedly returned a fetch response for ' . $action); + } + if ($matches[2] !== $expected) { + throw new RuntimeException(sprintf( + '%s was not shared across workers: expected %s, got %s', + $action, + $expected, + $matches[2] + )); + } + $checked++; + } + + if ($checked === 0) { + throw new RuntimeException('No non-seed worker handled ' . $action); + } +} + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.volatile_size_mb' => '32', + 'opcache.static_cache.persistent_size_mb' => '32', +]); +$tester->expectLogStartNotices(); + +$seedPid = parseSeedPid($tester->request(query: 'action=seed')->getBody()); + +expectCrossWorkerValue($tester, 'fetch_volatile', $seedPid, 'stored-value'); +expectCrossWorkerValue($tester, 'fetch_persistent', $seedPid, '42'); + +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +echo "Done\n"; + +?> +--EXPECT-- +Done +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_clear_invalidate_after_access_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_clear_invalidate_after_access_001.phpt new file mode 100644 index 000000000000..72c24177a388 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_clear_invalidate_after_access_001.phpt @@ -0,0 +1,313 @@ +--TEST-- +OPcache VolatileStatic tracking reference graphs are not republished after volatile_clear() or opcache_invalidate() in the same request under FPM +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- +holder = new stdClass(); + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingClearInvalidateClassState +{ + public static ?TrackingClearInvalidatePayload $payload = null; +} + +class TrackingClearInvalidatePropertyState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?TrackingClearInvalidatePayload $payload = null; +} + +class TrackingClearInvalidateMethodState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function payload(?TrackingClearInvalidatePayload $payload = null): ?TrackingClearInvalidatePayload + { + static $value = null; + + if ($payload !== null) { + $value = $payload; + } + + return $value; + } +} + +function tracking_clear_invalidate_set(string $kind, ?TrackingClearInvalidatePayload $payload): void +{ + switch ($kind) { + case 'class': + TrackingClearInvalidateClassState::$payload = $payload; + return; + + case 'property': + TrackingClearInvalidatePropertyState::$payload = $payload; + return; + + case 'method': + TrackingClearInvalidateMethodState::payload($payload); + return; + + default: + throw new RuntimeException('unknown kind'); + } +} + +function tracking_clear_invalidate_get(string $kind): ?TrackingClearInvalidatePayload +{ + return match ($kind) { + 'class' => TrackingClearInvalidateClassState::$payload, + 'property' => TrackingClearInvalidatePropertyState::$payload, + 'method' => TrackingClearInvalidateMethodState::payload(), + default => throw new RuntimeException('unknown kind'), + }; +} + +function tracking_clear_invalidate_apply(TrackingClearInvalidatePayload $payload, string $nodeName, string $childName, string $label): void +{ + $payload->objectRef->name = $nodeName; + $payload->objectRef->events[] = $label . '-object'; + $payload->arrayRef['child']->name = $childName; + $payload->arrayRef['child']->events[] = $label . '-child'; + $payload->holder->alias->events[] = $label . '-holder'; + $payload->log[] = $label; +} + +function tracking_clear_invalidate_seed(string $kind): void +{ + $leaf = new TrackingClearInvalidateNode('seed'); + $leaf->child = new TrackingClearInvalidateChild('child'); + + $payload = new TrackingClearInvalidatePayload(); + $payload->objectRef =& $leaf; + $payload->arrayRef = [ + 'alias' => &$leaf, + 'child' => &$leaf->child, + ]; + $payload->holder->alias =& $leaf; + $payload->holder->child =& $leaf->child; + $payload->log[] = 'seed'; + + tracking_clear_invalidate_set($kind, $payload); + tracking_clear_invalidate_apply($payload, 'seed-node', 'seed-child', 'seed'); +} + +function tracking_clear_invalidate_dump(string $kind, string $label): void +{ + $payload = tracking_clear_invalidate_get($kind); + if (!$payload instanceof TrackingClearInvalidatePayload) { + echo $kind, '@', $label, '=missing', "\n"; + return; + } + + echo $kind, '@', $label, '=', + $payload->objectRef->name, ',', + count($payload->objectRef->events), ',', + $payload->objectRef->child->name, ',', + count($payload->objectRef->child->events), ',', + count($payload->log), ',', + ($payload->objectRef === $payload->arrayRef['alias'] ? 'same' : 'copy'), ',', + ($payload->objectRef->child === $payload->arrayRef['child'] ? 'same' : 'copy'), ',', + ($payload->holder->alias === $payload->objectRef ? 'same' : 'copy'), ',', + ($payload->holder->child === $payload->objectRef->child ? 'same' : 'copy'), ',', + ($payload->holder->alias->child === $payload->objectRef->child ? 'same' : 'copy'), + "\n"; +} + +$action = $_GET['action'] ?? 'read'; +$kind = $_GET['kind'] ?? 'class'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + tracking_clear_invalidate_seed($kind); + tracking_clear_invalidate_dump($kind, 'seed'); + return; +} + +if ($action === 'clear_after_access') { + $payload = tracking_clear_invalidate_get($kind); + if (!$payload instanceof TrackingClearInvalidatePayload) { + throw new RuntimeException('missing payload'); + } + + tracking_clear_invalidate_apply($payload, 'clear-node', 'clear-child', 'clear'); + tracking_clear_invalidate_dump($kind, 'clear-before'); + OPcache\volatile_clear(); + echo "clear\n"; + return; +} + +if ($action === 'reset_after_access') { + $payload = tracking_clear_invalidate_get($kind); + if (!$payload instanceof TrackingClearInvalidatePayload) { + throw new RuntimeException('missing payload'); + } + + tracking_clear_invalidate_apply($payload, 'reset-node', 'reset-child', 'reset'); + tracking_clear_invalidate_dump($kind, 'reset-before'); + opcache_reset(); + echo "reset-after\n"; + return; +} + +if ($action === 'invalidate_after_access') { + $payload = tracking_clear_invalidate_get($kind); + if (!$payload instanceof TrackingClearInvalidatePayload) { + throw new RuntimeException('missing payload'); + } + + tracking_clear_invalidate_apply($payload, 'invalidate-node', 'invalidate-child', 'invalidate'); + tracking_clear_invalidate_dump($kind, 'invalidate-before'); + var_dump(opcache_invalidate(__FILE__, true)); + return; +} + +tracking_clear_invalidate_dump($kind, 'read'); +EOT; + +$args = []; +if (file_exists(ini_get('extension_dir') . '/opcache.so')) { + $args[] = '-dzend_extension=' . ini_get('extension_dir') . '/opcache.so'; +} +$args = [ + ...$args, + '-dopcache.enable=1', + '-dopcache.static_cache.volatile_size_mb=32', + '-dopcache.file_update_protection=0', + '-dopcache.jit=0', +]; + +$tester = new FPM\Tester($cfg, $code); +$tester->start($args); +$tester->expectLogStartNotices(); + +function tracking_clear_invalidate_fpm_request(FPM\Tester $tester, string $query): void +{ + echo $tester->request($query)->getBody(), "\n"; +} + +tracking_clear_invalidate_fpm_request($tester, 'action=reset'); +foreach (['class', 'property', 'method'] as $kind) { + tracking_clear_invalidate_fpm_request($tester, 'action=seed&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=read&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=clear_after_access&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=read&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=seed&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=read&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=reset_after_access&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=read&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=seed&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=read&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=invalidate_after_access&kind=' . $kind); + tracking_clear_invalidate_fpm_request($tester, 'action=read&kind=' . $kind); +} + +$tester->terminate(); +$tester->close(); +?> +--EXPECT-- +reset +class@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +class@read=seed-node,2,seed-child,1,2,same,same,same,same,same +class@clear-before=clear-node,4,clear-child,2,3,same,same,same,same,same +clear +class@read=missing +class@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +class@read=seed-node,2,seed-child,1,2,same,same,same,same,same +class@reset-before=reset-node,4,reset-child,2,3,same,same,same,same,same +reset-after +class@read=missing +class@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +class@read=seed-node,2,seed-child,1,2,same,same,same,same,same +class@invalidate-before=invalidate-node,4,invalidate-child,2,3,same,same,same,same,same +bool(true) +class@read=missing +property@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +property@read=seed-node,2,seed-child,1,2,same,same,same,same,same +property@clear-before=clear-node,4,clear-child,2,3,same,same,same,same,same +clear +property@read=missing +property@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +property@read=seed-node,2,seed-child,1,2,same,same,same,same,same +property@reset-before=reset-node,4,reset-child,2,3,same,same,same,same,same +reset-after +property@read=missing +property@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +property@read=seed-node,2,seed-child,1,2,same,same,same,same,same +property@invalidate-before=invalidate-node,4,invalidate-child,2,3,same,same,same,same,same +bool(true) +property@read=missing +method@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +method@read=seed-node,2,seed-child,1,2,same,same,same,same,same +method@clear-before=clear-node,4,clear-child,2,3,same,same,same,same,same +clear +method@read=missing +method@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +method@read=seed-node,2,seed-child,1,2,same,same,same,same,same +method@reset-before=reset-node,4,reset-child,2,3,same,same,same,same,same +reset-after +method@read=missing +method@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +method@read=seed-node,2,seed-child,1,2,same,same,same,same,same +method@invalidate-before=invalidate-node,4,invalidate-child,2,3,same,same,same,same,same +bool(true) +method@read=missing +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_reference_object_graph_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_reference_object_graph_001.phpt new file mode 100644 index 000000000000..0e0ba8466300 --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_reference_object_graph_001.phpt @@ -0,0 +1,252 @@ +--TEST-- +OPcache VolatileStatic tracking persists deep mutations through referenced object graphs under FPM +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- +holder = new stdClass(); + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingReferenceClassState +{ + public static ?TrackingReferencePayload $payload = null; +} + +class TrackingReferencePropertyState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?TrackingReferencePayload $payload = null; +} + +class TrackingReferenceMethodState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function payload(?TrackingReferencePayload $payload = null): ?TrackingReferencePayload + { + static $value = null; + + if ($payload !== null) { + $value = $payload; + } + + return $value; + } +} + +function tracking_reference_set(string $kind, ?TrackingReferencePayload $payload): void +{ + switch ($kind) { + case 'class': + TrackingReferenceClassState::$payload = $payload; + return; + + case 'property': + TrackingReferencePropertyState::$payload = $payload; + return; + + case 'method': + TrackingReferenceMethodState::payload($payload); + return; + + default: + throw new RuntimeException('unknown kind'); + } +} + +function tracking_reference_get(string $kind): ?TrackingReferencePayload +{ + return match ($kind) { + 'class' => TrackingReferenceClassState::$payload, + 'property' => TrackingReferencePropertyState::$payload, + 'method' => TrackingReferenceMethodState::payload(), + default => throw new RuntimeException('unknown kind'), + }; +} + +function tracking_reference_seed(string $kind): void +{ + $leaf = new TrackingReferenceNode('seed'); + $leaf->child = new TrackingReferenceChild('child'); + + $payload = new TrackingReferencePayload(); + $payload->objectRef =& $leaf; + $payload->arrayRef = [ + 'alias' => &$leaf, + 'child' => &$leaf->child, + ]; + $payload->holder->alias =& $leaf; + $payload->holder->child =& $leaf->child; + $payload->log[] = 'seed'; + + tracking_reference_set($kind, $payload); + + $leaf->name = 'seed-updated'; + $leaf->events[] = 'source-object'; + $leaf->child->name = 'child-updated'; + $leaf->child->events[] = 'source-child'; + $payload->arrayRef['alias']->events[] = 'array-alias'; + $payload->holder->alias->child->events[] = 'holder-alias-child'; + $payload->log[] = 'seed-mutated'; +} + +function tracking_reference_mutate(string $kind): void +{ + $payload = tracking_reference_get($kind); + if (!$payload instanceof TrackingReferencePayload) { + throw new RuntimeException('missing payload'); + } + + $payload->objectRef->name = 'mutated-again'; + $payload->objectRef->events[] = 'object-ref'; + $payload->arrayRef['child']->name = 'child-mutated-again'; + $payload->arrayRef['child']->events[] = 'array-child'; + $payload->holder->alias->events[] = 'holder-alias'; + $payload->holder->alias->child->events[] = 'holder-alias-child'; + $payload->log[] = 'mutated'; +} + +function tracking_reference_dump(string $kind, string $label): void +{ + $payload = tracking_reference_get($kind); + if (!$payload instanceof TrackingReferencePayload) { + echo $kind, '@', $label, '=missing', "\n"; + return; + } + + echo $kind, '@', $label, '=', + $payload->objectRef->name, ',', + count($payload->objectRef->events), ',', + $payload->objectRef->child->name, ',', + count($payload->objectRef->child->events), ',', + count($payload->log), ',', + ($payload->objectRef === $payload->arrayRef['alias'] ? 'same' : 'copy'), ',', + ($payload->objectRef->child === $payload->arrayRef['child'] ? 'same' : 'copy'), ',', + ($payload->holder->alias === $payload->objectRef ? 'same' : 'copy'), ',', + ($payload->holder->child === $payload->objectRef->child ? 'same' : 'copy'), ',', + ($payload->holder->alias->child === $payload->objectRef->child ? 'same' : 'copy'), + "\n"; +} + +$action = $_GET['action'] ?? 'read'; +$kind = $_GET['kind'] ?? 'class'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + tracking_reference_seed($kind); + tracking_reference_dump($kind, 'seed'); + return; +} + +if ($action === 'mutate') { + tracking_reference_mutate($kind); + tracking_reference_dump($kind, 'mutate'); + return; +} + +tracking_reference_dump($kind, 'read'); +EOT; + +$args = []; +if (file_exists(ini_get('extension_dir') . '/opcache.so')) { + $args[] = '-dzend_extension=' . ini_get('extension_dir') . '/opcache.so'; +} +$args = [ + ...$args, + '-dopcache.enable=1', + '-dopcache.static_cache.volatile_size_mb=32', + '-dopcache.file_update_protection=0', + '-dopcache.jit=0', +]; + +$tester = new FPM\Tester($cfg, $code); +$tester->start($args); +$tester->expectLogStartNotices(); + +function tracking_reference_fpm_request(FPM\Tester $tester, string $query): void +{ + echo $tester->request($query)->getBody(), "\n"; +} + +tracking_reference_fpm_request($tester, 'action=reset'); +foreach (['class', 'property', 'method'] as $kind) { + tracking_reference_fpm_request($tester, 'action=seed&kind=' . $kind); + tracking_reference_fpm_request($tester, 'action=read&kind=' . $kind); + tracking_reference_fpm_request($tester, 'action=mutate&kind=' . $kind); + tracking_reference_fpm_request($tester, 'action=read&kind=' . $kind); +} + +$tester->terminate(); +$tester->close(); +?> +--EXPECT-- +reset +class@seed=seed-updated,2,child-updated,2,2,same,same,same,same,same +class@read=seed-updated,2,child-updated,2,2,same,same,same,same,same +class@mutate=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +class@read=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +property@seed=seed-updated,2,child-updated,2,2,same,same,same,same,same +property@read=seed-updated,2,child-updated,2,2,same,same,same,same,same +property@mutate=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +property@read=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +method@seed=seed-updated,2,child-updated,2,2,same,same,same,same,same +method@read=seed-updated,2,child-updated,2,2,same,same,same,same,same +method@mutate=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +method@read=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_shared_reference_cell_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_shared_reference_cell_001.phpt new file mode 100644 index 000000000000..86c24f6c355f --- /dev/null +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_tracking_shared_reference_cell_001.phpt @@ -0,0 +1,323 @@ +--TEST-- +OPcache VolatileStatic tracking publishes shared reference-cell mutations before restore and detaches roots after restore under FPM +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- +holder = new stdClass(); + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingSharedReferenceClassA +{ + public static ?TrackingSharedReferencePayload $payload = null; +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingSharedReferenceClassB +{ + public static ?TrackingSharedReferencePayload $payload = null; +} + +class TrackingSharedReferencePropertyA +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?TrackingSharedReferencePayload $payload = null; +} + +class TrackingSharedReferencePropertyB +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?TrackingSharedReferencePayload $payload = null; +} + +class TrackingSharedReferenceMethodA +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function payload(?TrackingSharedReferencePayload $payload = null): ?TrackingSharedReferencePayload + { + static $value = null; + + if ($payload !== null) { + $value = $payload; + } + + return $value; + } +} + +class TrackingSharedReferenceMethodB +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function payload(?TrackingSharedReferencePayload $payload = null): ?TrackingSharedReferencePayload + { + static $value = null; + + if ($payload !== null) { + $value = $payload; + } + + return $value; + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingSharedReferenceBlob +{ + public static ?TrackingSharedReferencePayload $first = null; + public static ?TrackingSharedReferencePayload $second = null; +} + +function tracking_shared_reference_make_payload(string $label, TrackingSharedReferenceNode &$shared): TrackingSharedReferencePayload +{ + $payload = new TrackingSharedReferencePayload($label); + $payload->nodeRef =& $shared; + $payload->refs['node'] =& $shared; + $payload->refs['child'] =& $shared->child; + $payload->holder->node =& $shared; + $payload->holder->child =& $shared->child; + $payload->labels[] = $label; + + return $payload; +} + +function tracking_shared_reference_pair(string $kind): array +{ + return match ($kind) { + 'class' => [TrackingSharedReferenceClassA::$payload, TrackingSharedReferenceClassB::$payload], + 'property' => [TrackingSharedReferencePropertyA::$payload, TrackingSharedReferencePropertyB::$payload], + 'method' => [TrackingSharedReferenceMethodA::payload(), TrackingSharedReferenceMethodB::payload()], + 'blob' => [TrackingSharedReferenceBlob::$first, TrackingSharedReferenceBlob::$second], + default => throw new RuntimeException('unknown kind'), + }; +} + +function tracking_shared_reference_seed(string $kind): void +{ + $shared = new TrackingSharedReferenceNode('seed'); + $shared->child = new TrackingSharedReferenceChild('child'); + + $first = tracking_shared_reference_make_payload($kind . '-A', $shared); + $second = tracking_shared_reference_make_payload($kind . '-B', $shared); + + match ($kind) { + 'class' => [TrackingSharedReferenceClassA::$payload = $first, TrackingSharedReferenceClassB::$payload = $second], + 'property' => [TrackingSharedReferencePropertyA::$payload = $first, TrackingSharedReferencePropertyB::$payload = $second], + 'method' => [TrackingSharedReferenceMethodA::payload($first), TrackingSharedReferenceMethodB::payload($second)], + 'blob' => [TrackingSharedReferenceBlob::$first = $first, TrackingSharedReferenceBlob::$second = $second], + default => throw new RuntimeException('unknown kind'), + }; + + $first->nodeRef->name = 'seed-updated'; + $first->nodeRef->events[] = 'seed-node'; + $first->refs['child']->name = 'child-updated'; + $first->holder->child->events[] = 'seed-child'; + $first->labels[] = 'seed-mutated'; + $second->labels[] = 'seed-observed'; +} + +function tracking_shared_reference_mutate(string $kind): void +{ + [$first, $second] = tracking_shared_reference_pair($kind); + if (!$first instanceof TrackingSharedReferencePayload || !$second instanceof TrackingSharedReferencePayload) { + throw new RuntimeException('missing payload'); + } + + $first->holder->node->name = 'mutated-again'; + $first->holder->node->events[] = 'mutate-node'; + $first->nodeRef->child->name = 'child-mutated-again'; + $first->nodeRef->child->events[] = 'mutate-child'; + $first->labels[] = 'mutated-first'; +} + +function tracking_shared_reference_dump_payload(string $kind, int $index, ?TrackingSharedReferencePayload $payload): void +{ + if (!$payload instanceof TrackingSharedReferencePayload) { + echo $kind, '#', $index, '=missing', "\n"; + return; + } + + echo $kind, '#', $index, '=', + $payload->label, ',', + $payload->nodeRef->name, ',', + count($payload->nodeRef->events), ',', + $payload->nodeRef->child->name, ',', + count($payload->nodeRef->child->events), ',', + count($payload->labels), ',', + ($payload->nodeRef === $payload->refs['node'] ? 'same' : 'copy'), ',', + ($payload->nodeRef === $payload->holder->node ? 'same' : 'copy'), ',', + ($payload->nodeRef->child === $payload->refs['child'] ? 'same' : 'copy'), ',', + ($payload->nodeRef->child === $payload->holder->child ? 'same' : 'copy'), + "\n"; +} + +function tracking_shared_reference_dump(string $kind): void +{ + [$first, $second] = tracking_shared_reference_pair($kind); + echo $kind, '@cross=', + ($first instanceof TrackingSharedReferencePayload && $second instanceof TrackingSharedReferencePayload && $first->nodeRef === $second->nodeRef ? 'same' : 'copy'), ',', + ($first instanceof TrackingSharedReferencePayload && $second instanceof TrackingSharedReferencePayload && $first->nodeRef->child === $second->nodeRef->child ? 'same' : 'copy'), + "\n"; + tracking_shared_reference_dump_payload($kind, 0, $first); + tracking_shared_reference_dump_payload($kind, 1, $second); +} + +$action = $_GET['action'] ?? 'read'; +$kind = $_GET['kind'] ?? 'class'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + tracking_shared_reference_seed($kind); + tracking_shared_reference_dump($kind); + return; +} + +if ($action === 'mutate') { + tracking_shared_reference_mutate($kind); + tracking_shared_reference_dump($kind); + return; +} + +tracking_shared_reference_dump($kind); +EOT; + +$args = []; +if (file_exists(ini_get('extension_dir') . '/opcache.so')) { + $args[] = '-dzend_extension=' . ini_get('extension_dir') . '/opcache.so'; +} +$args = [ + ...$args, + '-dopcache.enable=1', + '-dopcache.static_cache.volatile_size_mb=32', + '-dopcache.file_update_protection=0', + '-dopcache.jit=0', +]; + +$tester = new FPM\Tester($cfg, $code); +$tester->start($args); +$tester->expectLogStartNotices(); + +function tracking_shared_reference_fpm_request(FPM\Tester $tester, string $query): void +{ + echo $tester->request($query)->getBody(), "\n"; +} + +tracking_shared_reference_fpm_request($tester, 'action=reset'); +foreach (['class', 'property', 'method', 'blob'] as $kind) { + tracking_shared_reference_fpm_request($tester, 'action=seed&kind=' . $kind); + tracking_shared_reference_fpm_request($tester, 'action=read&kind=' . $kind); + tracking_shared_reference_fpm_request($tester, 'action=mutate&kind=' . $kind); + tracking_shared_reference_fpm_request($tester, 'action=read&kind=' . $kind); +} + +$tester->terminate(); +$tester->close(); +?> +--EXPECT-- +reset +class@cross=same,same +class#0=class-A,seed-updated,1,child-updated,1,2,same,same,same,same +class#1=class-B,seed-updated,1,child-updated,1,2,same,same,same,same +class@cross=copy,copy +class#0=class-A,seed-updated,1,child-updated,1,2,same,same,same,same +class#1=class-B,seed-updated,1,child-updated,1,2,same,same,same,same +class@cross=copy,copy +class#0=class-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +class#1=class-B,seed-updated,1,child-updated,1,2,same,same,same,same +class@cross=copy,copy +class#0=class-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +class#1=class-B,seed-updated,1,child-updated,1,2,same,same,same,same +property@cross=same,same +property#0=property-A,seed-updated,1,child-updated,1,2,same,same,same,same +property#1=property-B,seed-updated,1,child-updated,1,2,same,same,same,same +property@cross=copy,copy +property#0=property-A,seed-updated,1,child-updated,1,2,same,same,same,same +property#1=property-B,seed-updated,1,child-updated,1,2,same,same,same,same +property@cross=copy,copy +property#0=property-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +property#1=property-B,seed-updated,1,child-updated,1,2,same,same,same,same +property@cross=copy,copy +property#0=property-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +property#1=property-B,seed-updated,1,child-updated,1,2,same,same,same,same +method@cross=same,same +method#0=method-A,seed-updated,1,child-updated,1,2,same,same,same,same +method#1=method-B,seed-updated,1,child-updated,1,2,same,same,same,same +method@cross=copy,copy +method#0=method-A,seed-updated,1,child-updated,1,2,same,same,same,same +method#1=method-B,seed-updated,1,child-updated,1,2,same,same,same,same +method@cross=copy,copy +method#0=method-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +method#1=method-B,seed-updated,1,child-updated,1,2,same,same,same,same +method@cross=copy,copy +method#0=method-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +method#1=method-B,seed-updated,1,child-updated,1,2,same,same,same,same +blob@cross=same,same +blob#0=blob-A,seed-updated,1,child-updated,1,2,same,same,same,same +blob#1=blob-B,seed-updated,1,child-updated,1,2,same,same,same,same +blob@cross=copy,copy +blob#0=blob-A,seed-updated,1,child-updated,1,2,same,same,same,same +blob#1=blob-B,seed-updated,1,child-updated,1,2,same,same,same,same +blob@cross=copy,copy +blob#0=blob-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +blob#1=blob-B,seed-updated,1,child-updated,1,2,same,same,same,same +blob@cross=copy,copy +blob#0=blob-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +blob#1=blob-B,seed-updated,1,child-updated,1,2,same,same,same,same +--CLEAN-- + diff --git a/ext/opcache/tests/fpm/tester.inc b/ext/opcache/tests/fpm/tester.inc new file mode 100644 index 000000000000..32536a265d2b --- /dev/null +++ b/ext/opcache/tests/fpm/tester.inc @@ -0,0 +1,4 @@ +. | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "php_config.h" +#endif + +#include +#include +#include +#include + +#include "Zend/zend_exceptions.h" +#include "Zend/zend_execute.h" +#include "Zend/zend_portability.h" +#include "sapi/embed/php_embed.h" + +#ifndef ZTS +# error "This helper requires a ZTS build" +#endif + +typedef enum { + THREAD_MODE_SEED, + THREAD_MODE_DELETE +} zend_opcache_thread_mode; + +typedef struct _zend_opcache_thread_ctx { + zend_opcache_thread_mode mode; + int result; + char message[128]; +} zend_opcache_thread_ctx; + +static const char opcache_test_ini[] = + "html_errors=0\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n" + "opcache.enable=1\n" + "opcache.enable_cli=1\n" + "opcache.memory_consumption=64\n" + "opcache.max_accelerated_files=200\n" + "opcache.static_cache.volatile_size_mb=8\n" + "opcache.static_cache.persistent_size_mb=8\n\0"; + +static const char init_code[] = + "(static function (): bool {" + " OPcache\\volatile_clear();" + " OPcache\\persistent_clear();" + " return true;" + "})()"; + +static const char seed_code[] = + "(static function (): bool {" + " if (!OPcache\\volatile_store('zts_v_first', str_repeat('A', 1800000))) return false;" + " if (!OPcache\\volatile_store('zts_v_second', str_repeat('B', 1800000))) return false;" + " if (!OPcache\\volatile_store('zts_v_third', str_repeat('C', 1800000))) return false;" + " OPcache\\persistent_store('zts_p_first', str_repeat('A', 1800000));" + " OPcache\\persistent_store('zts_p_second', str_repeat('B', 1800000));" + " OPcache\\persistent_store('zts_p_third', str_repeat('C', 1800000));" + " return true;" + "})()"; + +static const char delete_code[] = + "(static function (): bool {" + " OPcache\\volatile_delete('zts_v_second');" + " OPcache\\persistent_delete('zts_p_second');" + " return OPcache\\volatile_fetch('zts_v_second', 'missing') === 'missing'" + " && OPcache\\persistent_fetch('zts_p_second', 'missing') === 'missing';" + "})()"; + +static const char refill_code[] = + "(static function (): bool {" + " if (!OPcache\\volatile_store('zts_v_replacement', str_repeat('R', 1500000))) return false;" + " OPcache\\persistent_store('zts_p_replacement', str_repeat('R', 1500000));" + " return strlen(OPcache\\volatile_fetch('zts_v_first')) === 1800000" + " && strlen(OPcache\\volatile_fetch('zts_v_third')) === 1800000" + " && strlen(OPcache\\volatile_fetch('zts_v_replacement')) === 1500000" + " && strlen(OPcache\\persistent_fetch('zts_p_first')) === 1800000" + " && strlen(OPcache\\persistent_fetch('zts_p_third')) === 1800000" + " && strlen(OPcache\\persistent_fetch('zts_p_replacement')) === 1500000;" + "})()"; + +static void zend_opcache_thread_set_failure(zend_opcache_thread_ctx *ctx, const char *message) +{ + ctx->result = 1; + snprintf(ctx->message, sizeof(ctx->message), "%s", message); +} + +static int zend_opcache_test_startup(int argc, char **argv) +{ + if (!php_tsrm_startup_ex(4)) { + return FAILURE; + } + ZEND_TSRMLS_CACHE_UPDATE(); + + zend_signal_startup(); + sapi_startup(&php_embed_module); + php_embed_module.ini_entries = opcache_test_ini; + if (argv != NULL) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module) == FAILURE) { + sapi_shutdown(); + tsrm_shutdown(); + return FAILURE; + } + + SG(options) |= SAPI_OPTION_NO_CHDIR; + return SUCCESS; +} + +static void zend_opcache_test_shutdown(void) +{ + php_module_shutdown(); + sapi_shutdown(); + tsrm_shutdown(); +} + +static bool zend_opcache_thread_request_startup(void) +{ + (void) ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); + + SG(request_info).argc = 0; + SG(request_info).argv = NULL; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = "-"; + SG(request_info).path_translated = NULL; + SG(request_info).request_method = "GET"; + SG(request_info).no_headers = 1; + + if (php_request_startup() == FAILURE) { + return false; + } + + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_register_variable("PHP_SELF", "-", NULL); + return true; +} + +static bool zend_opcache_thread_eval(const char *code, zval *retval, const char *label) +{ + ZVAL_UNDEF(retval); + if (zend_eval_stringl_ex(code, strlen(code), retval, label, true) == FAILURE) { + if (EG(exception)) { + zend_clear_exception(); + } + return false; + } + + if (EG(exception)) { + zend_clear_exception(); + if (!Z_ISUNDEF_P(retval)) { + zval_ptr_dtor(retval); + ZVAL_UNDEF(retval); + } + return false; + } + + return true; +} + +static bool zend_opcache_thread_run_request_code(const char *code, const char *label, char *message, size_t message_size) +{ + zval retval; + + if (!zend_opcache_thread_request_startup()) { + snprintf(message, message_size, "%s: request startup failed", label); + return false; + } + + if (!zend_opcache_thread_eval(code, &retval, label)) { + snprintf(message, message_size, "%s: eval failed", label); + php_request_shutdown(NULL); + return false; + } + + if (!zend_is_true(&retval)) { + snprintf(message, message_size, "%s: returned false", label); + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return false; + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return true; +} + +static void *zend_opcache_thread_main(void *arg) +{ + zend_opcache_thread_ctx *ctx = (zend_opcache_thread_ctx *) arg; + const char *code = ctx->mode == THREAD_MODE_SEED ? seed_code : delete_code; + const char *label = ctx->mode == THREAD_MODE_SEED ? "zts explicit cache seed" : "zts explicit cache delete"; + + ctx->result = 0; + ctx->message[0] = '\0'; + if (!zend_opcache_thread_run_request_code(code, label, ctx->message, sizeof(ctx->message))) { + ctx->result = 1; + } + + ts_free_thread(); + return NULL; +} + +static bool zend_opcache_run_thread(zend_opcache_thread_mode mode, char *message, size_t message_size) +{ + zend_thread_t thread; + zend_opcache_thread_ctx ctx; + + ctx.mode = mode; + if (!zend_thread_start(&thread, zend_opcache_thread_main, &ctx)) { + snprintf(message, message_size, "thread creation failed"); + return false; + } + if (!zend_thread_join(&thread)) { + snprintf(message, message_size, "thread join failed"); + return false; + } + if (ctx.result != 0) { + snprintf(message, message_size, "%s", ctx.message); + return false; + } + + return true; +} + +int main(int argc, char **argv) +{ + char message[128]; + int exit_code; + + if (zend_opcache_test_startup(argc, argv) == FAILURE) { + fprintf(stderr, "startup failed\n"); + return 1; + } + + exit_code = 1; + message[0] = '\0'; + + if (!zend_opcache_thread_run_request_code(init_code, "zts explicit cache init", message, sizeof(message))) { + fprintf(stderr, "%s\n", message); + goto cleanup; + } + + if (!zend_opcache_run_thread(THREAD_MODE_SEED, message, sizeof(message))) { + fprintf(stderr, "%s\n", message); + goto cleanup; + } + + if (!zend_opcache_run_thread(THREAD_MODE_DELETE, message, sizeof(message))) { + fprintf(stderr, "%s\n", message); + goto cleanup; + } + + if (!zend_opcache_thread_run_request_code(refill_code, "zts explicit cache refill", message, sizeof(message))) { + fprintf(stderr, "%s\n", message); + goto cleanup; + } + + printf("ok\n"); + exit_code = 0; + +cleanup: + zend_opcache_test_shutdown(); + return exit_code; +} diff --git a/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c b/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c new file mode 100644 index 000000000000..0774b0ce6962 --- /dev/null +++ b/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c @@ -0,0 +1,304 @@ +#ifdef HAVE_CONFIG_H +# include "php_config.h" +#endif + +#include +#include +#include +#include + +#include "Zend/zend_exceptions.h" +#include "Zend/zend_execute.h" +#include "Zend/zend_portability.h" +#include "sapi/embed/php_embed.h" + +#ifndef ZTS +# error "This helper requires a ZTS build" +#endif + +typedef struct _zend_opcache_thread_ctx { + const char *mode; + const char *scenario_path; + const char *label; + int result; + char message[256]; +} zend_opcache_thread_ctx; + +static const char opcache_test_ini[] = + "html_errors=0\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n" + "opcache.enable=1\n" + "opcache.enable_cli=1\n" + "opcache.memory_consumption=64\n" + "opcache.max_accelerated_files=200\n" + "opcache.static_cache.persistent_size_mb=32\n\0"; + +static void zend_opcache_thread_set_failure(zend_opcache_thread_ctx *ctx, const char *message) +{ + ctx->result = 1; + snprintf(ctx->message, sizeof(ctx->message), "%s", message); +} + +static int zend_opcache_test_startup(int argc, char **argv) +{ + if (!php_tsrm_startup_ex(8)) { + return FAILURE; + } + ZEND_TSRMLS_CACHE_UPDATE(); + + zend_signal_startup(); + sapi_startup(&php_embed_module); + php_embed_module.ini_entries = opcache_test_ini; + if (argv != NULL) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module) == FAILURE) { + sapi_shutdown(); + tsrm_shutdown(); + return FAILURE; + } + + SG(options) |= SAPI_OPTION_NO_CHDIR; + return SUCCESS; +} + +static void zend_opcache_test_shutdown(void) +{ + php_module_shutdown(); + sapi_shutdown(); + tsrm_shutdown(); +} + +static bool zend_opcache_thread_request_startup(void) +{ + (void) ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); + + SG(request_info).argc = 0; + SG(request_info).argv = NULL; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = "-"; + SG(request_info).path_translated = NULL; + SG(request_info).request_method = "GET"; + SG(request_info).no_headers = 1; + + if (php_request_startup() == FAILURE) { + return false; + } + + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_register_variable("PHP_SELF", "-", NULL); + return true; +} + +static bool zend_opcache_thread_eval(const char *code, zval *retval, const char *label) +{ + ZVAL_UNDEF(retval); + if (zend_eval_stringl_ex(code, strlen(code), retval, label, true) == FAILURE) { + if (EG(exception)) { + zend_clear_exception(); + } + return false; + } + + if (EG(exception)) { + zend_clear_exception(); + if (!Z_ISUNDEF_P(retval)) { + zval_ptr_dtor(retval); + ZVAL_UNDEF(retval); + } + return false; + } + + return true; +} + +static bool zend_opcache_thread_run_request_code(const char *code, const char *label, char *message, size_t message_size) +{ + zval retval; + + if (!zend_opcache_thread_request_startup()) { + snprintf(message, message_size, "%s: request startup failed", label); + return false; + } + + if (!zend_opcache_thread_eval(code, &retval, label)) { + snprintf(message, message_size, "%s: eval failed", label); + php_request_shutdown(NULL); + return false; + } + + if (!zend_is_true(&retval)) { + snprintf(message, message_size, "%s: returned false", label); + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return false; + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return true; +} + +static char *zend_opcache_build_scenario_code(const char *mode, const char *scenario_path) +{ + char *code; + size_t code_len; + + code_len = strlen(mode) + strlen(scenario_path) + sizeof("$mode=''; return require '';\0"); + code = malloc(code_len); + if (code == NULL) { + return NULL; + } + + snprintf(code, code_len, "$mode='%s'; return require '%s';", mode, scenario_path); + return code; +} + +static bool zend_opcache_run_request_mode(const char *scenario_path, const char *mode, const char *label, char *message, size_t message_size) +{ + char *code; + bool result; + + code = zend_opcache_build_scenario_code(mode, scenario_path); + if (code == NULL) { + snprintf(message, message_size, "%s: failed to build scenario code", label); + return false; + } + + result = zend_opcache_thread_run_request_code(code, label, message, message_size); + free(code); + return result; +} + +static bool zend_opcache_write_done_file(const char *scenario_path) +{ + char *done_path; + size_t done_path_len; + FILE *fp; + + done_path_len = strlen(scenario_path) + sizeof(".done"); + done_path = malloc(done_path_len); + if (done_path == NULL) { + return false; + } + snprintf(done_path, done_path_len, "%s.done", scenario_path); + + fp = fopen(done_path, "wb"); + free(done_path); + if (fp == NULL) { + return false; + } + fputs("done", fp); + fclose(fp); + return true; +} + +static void *zend_opcache_thread_main(void *arg) +{ + zend_opcache_thread_ctx *ctx; + + ctx = (zend_opcache_thread_ctx *) arg; + ctx->result = 0; + ctx->message[0] = '\0'; + + if (!zend_opcache_run_request_mode(ctx->scenario_path, ctx->mode, ctx->label, ctx->message, sizeof(ctx->message))) { + ctx->result = 1; + ts_free_thread(); + return NULL; + } + + if (strcmp(ctx->mode, "writer") == 0 && !zend_opcache_write_done_file(ctx->scenario_path)) { + zend_opcache_thread_set_failure(ctx, "writer failed to signal completion"); + } + + ts_free_thread(); + return NULL; +} + +static bool zend_opcache_run_threaded_scenario(const char *scenario_path, char *message, size_t message_size) +{ + zend_thread_t reader_thread; + zend_thread_t writer_thread; + zend_opcache_thread_ctx reader_ctx; + zend_opcache_thread_ctx writer_ctx; + + if (!zend_opcache_run_request_mode(scenario_path, "init", "zts persistent_static init", message, message_size)) { + return false; + } + + reader_ctx.mode = "reader"; + reader_ctx.scenario_path = scenario_path; + reader_ctx.label = "zts persistent_static reader"; + if (!zend_thread_start(&reader_thread, zend_opcache_thread_main, &reader_ctx)) { + snprintf(message, message_size, "reader thread creation failed"); + return false; + } + + writer_ctx.mode = "writer"; + writer_ctx.scenario_path = scenario_path; + writer_ctx.label = "zts persistent_static writer"; + if (!zend_thread_start(&writer_thread, zend_opcache_thread_main, &writer_ctx)) { + snprintf(message, message_size, "writer thread creation failed"); + return false; + } + + if (!zend_thread_join(&writer_thread)) { + snprintf(message, message_size, "writer thread join failed"); + return false; + } + if (!zend_thread_join(&reader_thread)) { + snprintf(message, message_size, "reader thread join failed"); + return false; + } + if (writer_ctx.result != 0) { + snprintf(message, message_size, "%s", writer_ctx.message); + return false; + } + if (reader_ctx.result != 0) { + snprintf(message, message_size, "%s", reader_ctx.message); + return false; + } + + return zend_opcache_run_request_mode(scenario_path, "verify", "zts persistent_static verify", message, message_size); +} + +int main(int argc, char **argv) +{ + char message[256]; + int exit_code; + + if (argc < 2) { + fprintf(stderr, "scenario path required\n"); + return 1; + } + + if (zend_opcache_test_startup(argc, argv) == FAILURE) { + fprintf(stderr, "startup failed\n"); + return 1; + } + + exit_code = 1; + message[0] = '\0'; + + if (!zend_opcache_run_threaded_scenario(argv[1], message, sizeof(message))) { + fprintf(stderr, "%s\n", message); + goto cleanup; + } + + printf("ok\n"); + exit_code = 0; + +cleanup: + zend_opcache_test_shutdown(); + return exit_code; +} diff --git a/ext/opcache/tests/helpers/persistent_static_zts_threads_001.inc b/ext/opcache/tests/helpers/persistent_static_zts_threads_001.inc new file mode 100644 index 000000000000..ce2c0b52beff --- /dev/null +++ b/ext/opcache/tests/helpers/persistent_static_zts_threads_001.inc @@ -0,0 +1,100 @@ + 0, 'numbers' => []]; + + public static function next(): int + { + static $value = 0; + + return ++$value; + } +} + +class ZtsRacePropertyState +{ + #[OPcache\PersistentStatic] + public static array $data = ['value' => 0, 'numbers' => []]; +} + +class ZtsRaceMethodState +{ + #[OPcache\PersistentStatic] + public static function next(): int + { + static $value = 0; + + return ++$value; + } +} + +function zts_persistent_static_wait_for_file(string $path): bool +{ + $deadline = microtime(true) + 5.0; + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + return false; + } + usleep(1000); + } + return true; +} + +function zts_persistent_static_tuple(): string +{ + return ZtsRaceClassState::$data['value'] . ',' + . array_sum(ZtsRaceClassState::$data['numbers']) . ',' + . ZtsRaceClassState::next() . ',' + . ZtsRacePropertyState::$data['value'] . ',' + . array_sum(ZtsRacePropertyState::$data['numbers']) . ',' + . ZtsRaceMethodState::next(); +} + +$readyFile = __FILE__ . '.ready'; +$doneFile = __FILE__ . '.done'; + +if ($mode === 'init') { + @unlink($readyFile); + @unlink($doneFile); + opcache_reset(); + ZtsRaceClassState::$data = ['value' => 1, 'numbers' => [1]]; + ZtsRaceClassState::next(); + ZtsRacePropertyState::$data = ['value' => 1, 'numbers' => [1]]; + ZtsRaceMethodState::next(); + return true; +} + +if ($mode === 'writer') { + if (!zts_persistent_static_wait_for_file($readyFile)) { + return false; + } + $classMethod = ZtsRaceClassState::next(); + ZtsRaceClassState::$data['value'] = 10; + ZtsRaceClassState::$data['numbers'][] = 20; + ZtsRacePropertyState::$data['value'] = 10; + ZtsRacePropertyState::$data['numbers'][] = 20; + $method = ZtsRaceMethodState::next(); + return $classMethod === 2 + && $method === 2 + && ZtsRaceClassState::$data['value'] === 10 + && array_sum(ZtsRaceClassState::$data['numbers']) === 21 + && ZtsRacePropertyState::$data['value'] === 10 + && array_sum(ZtsRacePropertyState::$data['numbers']) === 21; +} + +if ($mode === 'reader') { + file_put_contents($readyFile, 'ready'); + if (!zts_persistent_static_wait_for_file($doneFile)) { + return false; + } + return zts_persistent_static_tuple() === '10,21,3,1,1,3'; +} + +if ($mode === 'verify') { + @unlink($readyFile); + @unlink($doneFile); + return zts_persistent_static_tuple() === '10,21,4,1,1,4'; +} + +return false; diff --git a/ext/opcache/tests/helpers/volatile_cache_process_lock_mode_001.c b/ext/opcache/tests/helpers/volatile_cache_process_lock_mode_001.c new file mode 100644 index 000000000000..ca0ad94e69a7 --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_process_lock_mode_001.c @@ -0,0 +1,300 @@ +#ifdef HAVE_CONFIG_H +# include "php_config.h" +# undef HAVE_CONFIG_H +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include "sapi/embed/php_embed.h" +#include "zend_static_cache_internal.h" + +#ifdef ZEND_WIN32 +# error "This helper requires the POSIX static cache lock implementation" +#endif + +#define READER_HOLD_USEC 100000 +#define WRITER_EARLY_CHECK_USEC 20000 +#define WRITER_MIN_BLOCK_USEC 40000 + +static const char opcache_test_ini[] = + "html_errors=0\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n" + "opcache.enable=1\n" + "opcache.enable_cli=1\n" + "opcache.memory_consumption=64\n" + "opcache.max_accelerated_files=200\n" + "opcache.static_cache.volatile_size_mb=32\n\0"; + +static int zend_opcache_test_startup(int argc, char **argv) +{ +#ifdef ZTS + if (!php_tsrm_startup_ex(2)) { + return FAILURE; + } + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + zend_signal_startup(); + sapi_startup(&php_embed_module); + php_embed_module.ini_entries = opcache_test_ini; + if (argv != NULL) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module) == FAILURE) { + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + return FAILURE; + } + + SG(options) |= SAPI_OPTION_NO_CHDIR; + + return SUCCESS; +} + +static void zend_opcache_test_shutdown(void) +{ + php_module_shutdown(); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif +} + +static void fail_with_message(const char *message) +{ + fprintf(stderr, "%s\n", message); + exit(1); +} + +static bool verify_lock_ready(void) +{ + if (!zend_opcache_static_cache_wlock()) { + return false; + } + + if (!zend_opcache_static_cache_header_init_locked()) { + zend_opcache_static_cache_unlock(); + return false; + } + + zend_opcache_static_cache_unlock(); + + return true; +} + +static bool read_exact(int fd, void *buffer, size_t length) +{ + unsigned char *cursor = (unsigned char *) buffer; + ssize_t bytes_read; + size_t total = 0; + + while (total < length) { + bytes_read = read(fd, cursor + total, length - total); + if (bytes_read <= 0) { + return false; + } + + total += (size_t) bytes_read; + } + + return true; +} + +static void reader_process(int write_fd) +{ + char signal = 'r'; + + if (!zend_opcache_static_cache_rlock()) { + _exit(10); + } + + if (write(write_fd, &signal, sizeof(signal)) != (ssize_t) sizeof(signal)) { + zend_opcache_static_cache_unlock(); + _exit(11); + } + + usleep(READER_HOLD_USEC); + zend_opcache_static_cache_unlock(); + _exit(0); +} + +static void writer_process(int write_fd) +{ + struct timeval start_time, end_time; + uint64_t elapsed_usec; + + if (gettimeofday(&start_time, NULL) != 0) { + _exit(20); + } + + if (!zend_opcache_static_cache_wlock()) { + _exit(21); + } + + if (gettimeofday(&end_time, NULL) != 0) { + zend_opcache_static_cache_unlock(); + _exit(22); + } + + elapsed_usec = (uint64_t) (end_time.tv_sec - start_time.tv_sec) * 1000000ULL; + elapsed_usec += (uint64_t) (end_time.tv_usec - start_time.tv_usec); + + if (write(write_fd, &elapsed_usec, sizeof(elapsed_usec)) != (ssize_t) sizeof(elapsed_usec)) { + zend_opcache_static_cache_unlock(); + _exit(23); + } + + zend_opcache_static_cache_unlock(); + _exit(0); +} + +static void require_child_success(pid_t pid, const char *label) +{ + int status; + + if (waitpid(pid, &status, 0) != pid) { + fail_with_message("waitpid failed"); + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + fprintf(stderr, "%s exited with status %d\n", label, WIFEXITED(status) ? WEXITSTATUS(status) : -1); + exit(1); + } +} + +int main(int argc, char **argv) +{ + int reader_pipe[2], writer_pipe[2], flags; + char reader_signal[2]; + uint64_t writer_elapsed = 0; + ssize_t bytes_read; + pid_t reader_one, reader_two, writer; + + if (zend_opcache_test_startup(argc, argv) == FAILURE) { + fail_with_message("startup failed"); + } + + if (!verify_lock_ready()) { + zend_opcache_test_shutdown(); + fail_with_message("initial lock verification failed"); + } + + if (pipe(reader_pipe) != 0 || pipe(writer_pipe) != 0) { + zend_opcache_test_shutdown(); + fail_with_message("pipe creation failed"); + } + + reader_one = fork(); + if (reader_one == 0) { + close(reader_pipe[0]); + close(writer_pipe[0]); + close(writer_pipe[1]); + reader_process(reader_pipe[1]); + } + if (reader_one < 0) { + zend_opcache_test_shutdown(); + fail_with_message("first reader fork failed"); + } + + reader_two = fork(); + if (reader_two == 0) { + close(reader_pipe[0]); + close(writer_pipe[0]); + close(writer_pipe[1]); + reader_process(reader_pipe[1]); + } + if (reader_two < 0) { + kill(reader_one, SIGTERM); + require_child_success(reader_one, "reader_one"); + zend_opcache_test_shutdown(); + fail_with_message("second reader fork failed"); + } + + close(reader_pipe[1]); + if (!read_exact(reader_pipe[0], reader_signal, sizeof(reader_signal))) { + kill(reader_one, SIGTERM); + kill(reader_two, SIGTERM); + require_child_success(reader_one, "reader_one"); + require_child_success(reader_two, "reader_two"); + zend_opcache_test_shutdown(); + fail_with_message("readers did not acquire the lock"); + } + + writer = fork(); + if (writer == 0) { + close(reader_pipe[0]); + close(writer_pipe[0]); + writer_process(writer_pipe[1]); + } + if (writer < 0) { + kill(reader_one, SIGTERM); + kill(reader_two, SIGTERM); + require_child_success(reader_one, "reader_one"); + require_child_success(reader_two, "reader_two"); + zend_opcache_test_shutdown(); + fail_with_message("writer fork failed"); + } + + close(writer_pipe[1]); + usleep(WRITER_EARLY_CHECK_USEC); + flags = fcntl(writer_pipe[0], F_GETFL, 0); + if (flags >= 0) { + fcntl(writer_pipe[0], F_SETFL, flags | O_NONBLOCK); + } + bytes_read = read(writer_pipe[0], &writer_elapsed, sizeof(writer_elapsed)); + if (bytes_read > 0) { + kill(reader_one, SIGTERM); + kill(reader_two, SIGTERM); + kill(writer, SIGTERM); + require_child_success(reader_one, "reader_one"); + require_child_success(reader_two, "reader_two"); + require_child_success(writer, "writer"); + zend_opcache_test_shutdown(); + fail_with_message("writer acquired the lock before readers released it"); + } + if (flags >= 0) { + fcntl(writer_pipe[0], F_SETFL, flags); + } + + require_child_success(reader_one, "reader_one"); + require_child_success(reader_two, "reader_two"); + if (!read_exact(writer_pipe[0], &writer_elapsed, sizeof(writer_elapsed))) { + kill(writer, SIGTERM); + require_child_success(writer, "writer"); + zend_opcache_test_shutdown(); + fail_with_message("writer did not report timing"); + } + close(reader_pipe[0]); + close(writer_pipe[0]); + require_child_success(writer, "writer"); + + if (writer_elapsed < WRITER_MIN_BLOCK_USEC) { + zend_opcache_test_shutdown(); + fail_with_message("writer was not blocked by active readers"); + } + + printf("ok\n"); + zend_opcache_test_shutdown(); + + return 0; +} diff --git a/ext/opcache/tests/helpers/volatile_cache_retired_shared_graph_free_001.c b/ext/opcache/tests/helpers/volatile_cache_retired_shared_graph_free_001.c new file mode 100644 index 000000000000..960045d2b2da --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_retired_shared_graph_free_001.c @@ -0,0 +1,304 @@ +#ifdef HAVE_CONFIG_H +# include "php_config.h" +# undef HAVE_CONFIG_H +#endif + +#include +#include +#include +#include +#include + +#include "sapi/embed/php_embed.h" +#include "zend_static_cache_internal.h" + +#define TEST_KEY "retired_shared_graph_free_payload" + +static const char opcache_test_ini[] = + "html_errors=0\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n" + "opcache.enable=1\n" + "opcache.enable_cli=1\n" + "opcache.memory_consumption=64\n" + "opcache.max_accelerated_files=200\n" + "opcache.static_cache.volatile_size_mb=32\n\0"; + +static const char seed_code[] = + "(static function (): bool {" + " OPcache\\volatile_clear();" + " $payload = ['name' => 'retired-shared-graph-free', 'items' => []];" + " for ($i = 0; $i < 128; $i++) {" + " $payload['items'][] = ['id' => $i, 'path' => '/items/' . $i, 'flags' => [true, false, true]];" + " }" + " return OPcache\\volatile_store('" TEST_KEY "', $payload);" + "})()"; + +static int zend_opcache_test_startup(int argc, char **argv) +{ +#ifdef ZTS + if (!php_tsrm_startup_ex(2)) { + return FAILURE; + } + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + zend_signal_startup(); + sapi_startup(&php_embed_module); + php_embed_module.ini_entries = opcache_test_ini; + if (argv != NULL) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module) == FAILURE) { + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + return FAILURE; + } + + SG(options) |= SAPI_OPTION_NO_CHDIR; + + return SUCCESS; +} + +static void zend_opcache_test_shutdown(void) +{ + php_module_shutdown(); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif +} + +static void fail_with_message(const char *message) +{ + fprintf(stderr, "%s\n", message); + exit(1); +} + +static bool zend_opcache_test_request_startup(void) +{ +#ifdef ZTS + (void) ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + SG(request_info).argc = 0; + SG(request_info).argv = NULL; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = "-"; + SG(request_info).path_translated = NULL; + SG(request_info).request_method = "GET"; + SG(request_info).no_headers = 1; + + if (php_request_startup() == FAILURE) { + return false; + } + + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_register_variable("PHP_SELF", "-", NULL); + + return true; +} + +static bool zend_opcache_test_eval_bool(const char *code, const char *label) +{ + zval retval; + bool result = false; + + ZVAL_UNDEF(&retval); + if (zend_eval_stringl_ex(code, strlen(code), &retval, label, true) == SUCCESS && !EG(exception)) { + result = zend_is_true(&retval); + } + + if (EG(exception)) { + zend_clear_exception(); + } + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + + return result; +} + +static bool zend_opcache_test_run_request_bool(const char *code, const char *label) +{ + bool result; + + if (!zend_opcache_test_request_startup()) { + return false; + } + + result = zend_opcache_test_eval_bool(code, label); + php_request_shutdown(NULL); + + return result; +} + +static bool zend_opcache_test_locate_payload(uint32_t *payload_offset) +{ + zend_opcache_static_cache_context *previous_context; + zend_opcache_static_cache_header *header; + zend_opcache_static_cache_entry *entries; + zend_string *key; + zend_ulong hash; + uint32_t index; + bool found = false; + + key = zend_string_init(TEST_KEY, sizeof(TEST_KEY) - 1, false); + hash = zend_string_hash_val(key); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + + if (zend_opcache_static_cache_rlock()) { + if (zend_opcache_static_cache_header_is_initialized_locked()) { + header = zend_opcache_static_cache_header_ptr(); + entries = zend_opcache_static_cache_entries(header); + for (index = 0; index < header->capacity; index++) { + if (zend_opcache_static_cache_key_equals(&entries[index], key, hash)) { + if (entries[index].value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { + *payload_offset = entries[index].value_offset; + found = true; + } + break; + } + } + } + zend_opcache_static_cache_unlock(); + } + + zend_opcache_static_cache_restore_context(previous_context); + zend_string_release(key); + + return found; +} + +static bool zend_opcache_test_payload_region_is_free_locked(uint32_t payload_offset, bool *is_free) +{ + zend_opcache_static_cache_header *header; + zend_opcache_static_cache_block *block; + uint32_t block_offset, free_offset; + + if (payload_offset < sizeof(zend_opcache_static_cache_block)) { + return false; + } + + header = zend_opcache_static_cache_header_ptr(); + if (header == NULL) { + return false; + } + + block_offset = payload_offset - (uint32_t) sizeof(zend_opcache_static_cache_block); + *is_free = false; + if (block_offset >= header->data_offset + header->next_free) { + *is_free = true; + + return true; + } + + free_offset = header->free_list; + while (free_offset != 0) { + block = zend_opcache_static_cache_block_ptr(free_offset); + if (zend_opcache_static_cache_block_is_free(block) && + block_offset >= free_offset && + block_offset < free_offset + block->size + ) { + *is_free = true; + break; + } + free_offset = block->next_free; + } + + return true; +} + +static bool zend_opcache_test_payload_region_is_free(uint32_t payload_offset, bool *is_free) +{ + zend_opcache_static_cache_context *previous_context; + bool result = false; + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + if (zend_opcache_static_cache_rlock()) { + if (zend_opcache_static_cache_header_is_initialized_locked()) { + result = zend_opcache_test_payload_region_is_free_locked(payload_offset, is_free); + } + zend_opcache_static_cache_unlock(); + } + zend_opcache_static_cache_restore_context(previous_context); + + return result; +} + +static bool zend_opcache_test_queue_deferred_free(uint32_t payload_offset) +{ + zend_opcache_static_cache_context *previous_context; + bool is_free = false; + bool result = false; + + if (!zend_opcache_test_request_startup()) { + return false; + } + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + if (zend_opcache_static_cache_rlock()) { + if (zend_opcache_static_cache_header_is_initialized_locked()) { + zend_opcache_static_cache_defer_retired_shared_graph_free(payload_offset); + result = zend_opcache_test_payload_region_is_free_locked(payload_offset, &is_free) && !is_free; + } + zend_opcache_static_cache_unlock(); + } + zend_opcache_static_cache_restore_context(previous_context); + + if (result) { + zend_opcache_static_cache_release_request_shared_graph_refs(); + result = zend_opcache_test_payload_region_is_free(payload_offset, &is_free) && is_free; + } + + php_request_shutdown(NULL); + + return result; +} + +int main(int argc, char **argv) +{ + uint32_t payload_offset = 0; + bool is_free = true; + + if (zend_opcache_test_startup(argc, argv) == FAILURE) { + fail_with_message("startup failed"); + } + + if (!zend_opcache_test_run_request_bool(seed_code, "seed shared graph")) { + zend_opcache_test_shutdown(); + fail_with_message("failed to seed shared graph payload"); + } + + if (!zend_opcache_test_locate_payload(&payload_offset)) { + zend_opcache_test_shutdown(); + fail_with_message("failed to locate shared graph payload"); + } + + if (!zend_opcache_test_payload_region_is_free(payload_offset, &is_free) || is_free) { + zend_opcache_test_shutdown(); + fail_with_message("shared graph payload was unexpectedly free before deferred cleanup"); + } + + if (!zend_opcache_test_queue_deferred_free(payload_offset)) { + zend_opcache_test_shutdown(); + fail_with_message("deferred cleanup did not free payload after the read lock was released"); + } + + if (!zend_opcache_test_payload_region_is_free(payload_offset, &is_free) || !is_free) { + zend_opcache_test_shutdown(); + fail_with_message("deferred cleanup did not leave the payload block free"); + } + + zend_opcache_test_shutdown(); + puts("ok"); + + return 0; +} diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c b/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c new file mode 100644 index 000000000000..cacde6cfb164 --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c @@ -0,0 +1,476 @@ +#ifdef HAVE_CONFIG_H +# include "php_config.h" +# undef HAVE_CONFIG_H +#endif + +#include +#include +#include + +#include "Zend/zend_atomic.h" +#include "Zend/zend_exceptions.h" +#include "Zend/zend_execute.h" +#include "Zend/zend_portability.h" + +#include "sapi/embed/php_embed.h" +#include "zend_static_cache_internal.h" + +#ifndef ZTS +# error "This helper requires a ZTS build" +#endif + +#define REQUEST_COUNT 4 + +typedef struct _zend_opcache_test_accel_directives { + zend_long memory_consumption; + zend_long static_cache_volatile_size_mb; + zend_long max_accelerated_files; + double max_wasted_percentage; + char *user_blacklist_filename; + zend_long force_restart_timeout; + bool use_cwd; + bool ignore_dups; + bool validate_timestamps; + bool revalidate_path; + bool save_comments; + bool record_warnings; + bool protect_memory; + bool file_override_enabled; + bool enable_cli; + bool validate_permission; +#ifndef ZEND_WIN32 + bool validate_root; +#endif + zend_ulong revalidate_freq; + zend_ulong file_update_protection; + char *error_log; +#ifdef ZEND_WIN32 + char *mmap_base; +#endif + char *memory_model; + zend_long log_verbosity_level; + zend_long optimization_level; + zend_long opt_debug_level; + zend_long max_file_size; + zend_long interned_strings_buffer; + char *restrict_api; +#ifndef ZEND_WIN32 + char *lockfile_path; +#endif + char *file_cache; + bool file_cache_read_only; + bool file_cache_only; + bool file_cache_consistency_checks; +#if ENABLE_FILE_CACHE_FALLBACK + bool file_cache_fallback; +#endif +#ifdef HAVE_HUGE_CODE_PAGES + bool huge_code_pages; +#endif + char *preload; +#ifndef ZEND_WIN32 + char *preload_user; +#endif +#ifdef ZEND_WIN32 + char *cache_id; +#endif +} zend_opcache_test_accel_directives; + +typedef struct _zend_opcache_test_globals { + bool counted; + bool enabled; + bool locked; + bool accelerator_enabled; + bool pcre_reseted; + zend_opcache_test_accel_directives accel_directives; +} zend_opcache_test_globals; + +typedef struct _zend_opcache_ref_thread_ctx { + int result; + char message[256]; +} zend_opcache_ref_thread_ctx; + +extern size_t accel_globals_offset; + +#define ZEND_OPCACHE_TEST_CG(v) ZEND_TSRMG_FAST(accel_globals_offset, zend_opcache_test_globals *, v) + +static const char opcache_test_ini[] = + "html_errors=0\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n" + "opcache.enable=1\n" + "opcache.enable_cli=1\n" + "opcache.memory_consumption=64\n" + "opcache.max_accelerated_files=200\n" + "opcache.static_cache.volatile_size_mb=32\n\0"; + +static const char clear_code[] = + "(static function (): bool {" + " OPcache\\volatile_clear();" + " return true;" + "})()"; + +static const char worker_code[] = + "(static function (): bool {" + " $payload = ['name' => 'compiled-route-table', 'routes' => [], 'generators' => []];" + " for ($i = 0; $i < 192; $i++) {" + " $path = '/catalog/books/' . $i;" + " $payload['routes'][$path] = [" + " 'controller' => 'CatalogController::show'," + " 'methods' => ['GET', 'POST']," + " 'flags' => [true, false, true]," + " 'score' => $i," + " ];" + " $payload['generators'][$path] = [" + " 'pattern' => $path," + " 'variables' => ['id' => $i]," + " ];" + " }" + " for ($i = 0; $i < 16; $i++) {" + " if (!OPcache\\volatile_store('zts_request_shared_graph_refs_payload', $payload)) {" + " return false;" + " }" + " }" + " for ($i = 0; $i < 64; $i++) {" + " $fetched = OPcache\\volatile_fetch('zts_request_shared_graph_refs_payload');" + " if (!is_array($fetched)" + " || !isset($fetched['routes'], $fetched['generators'])" + " || count($fetched['routes']) !== 192) {" + " return false;" + " }" + " }" + " return true;" + "})()"; + +static zend_opcache_test_accel_directives zend_opcache_thread_accel_directives; +static bool zend_opcache_thread_enabled; + +static const zend_opcache_static_cache_shared_graph_header *zend_opcache_locate_shared_graph_header(uint32_t payload_offset) +{ + const unsigned char *buffer; + size_t buffer_len; + size_t padding; + uintptr_t raw_address, aligned_address; + const zend_opcache_static_cache_shared_graph_header *header; + + buffer_len = zend_opcache_static_cache_block_payload_capacity(payload_offset); + if (buffer_len == 0) { + return NULL; + } + + buffer = (const unsigned char *) zend_opcache_static_cache_ptr(payload_offset); + raw_address = (uintptr_t) buffer; + aligned_address = (uintptr_t) ZEND_MM_ALIGNED_SIZE(raw_address); + padding = (size_t) (aligned_address - raw_address); + if (padding > buffer_len || buffer_len - padding < sizeof(*header)) { + return NULL; + } + + buffer += padding; + header = (const zend_opcache_static_cache_shared_graph_header *) buffer; + if (header->magic != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_MAGIC || + header->version != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VERSION) { + return NULL; + } + + return header; +} + +static int zend_opcache_test_startup(int argc, char **argv) +{ + if (!php_tsrm_startup_ex(3)) { + return FAILURE; + } + ZEND_TSRMLS_CACHE_UPDATE(); + + zend_signal_startup(); + sapi_startup(&php_embed_module); + php_embed_module.ini_entries = opcache_test_ini; + if (argv != NULL) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module) == FAILURE) { + sapi_shutdown(); + tsrm_shutdown(); + return FAILURE; + } + + zend_opcache_thread_accel_directives = ZEND_OPCACHE_TEST_CG(accel_directives); + zend_opcache_thread_enabled = ZEND_OPCACHE_TEST_CG(enabled); + + SG(options) |= SAPI_OPTION_NO_CHDIR; + return SUCCESS; +} + +static void zend_opcache_test_shutdown(void) +{ + php_module_shutdown(); + sapi_shutdown(); + tsrm_shutdown(); +} + +static bool zend_opcache_thread_request_startup(void) +{ + (void) ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); + ZEND_OPCACHE_TEST_CG(accel_directives) = zend_opcache_thread_accel_directives; + ZEND_OPCACHE_TEST_CG(enabled) = zend_opcache_thread_enabled; + + SG(request_info).argc = 0; + SG(request_info).argv = NULL; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = "-"; + SG(request_info).path_translated = NULL; + SG(request_info).request_method = "GET"; + SG(request_info).no_headers = 1; + + if (php_request_startup() == FAILURE) { + return false; + } + + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_register_variable("PHP_SELF", "-", NULL); + return true; +} + +static bool zend_opcache_thread_eval(const char *code, zval *retval, const char *label) +{ + ZVAL_UNDEF(retval); + if (zend_eval_stringl_ex(code, strlen(code), retval, label, true) == FAILURE) { + if (EG(exception)) { + zend_clear_exception(); + } + return false; + } + + if (EG(exception)) { + zend_clear_exception(); + if (!Z_ISUNDEF_P(retval)) { + zval_ptr_dtor(retval); + ZVAL_UNDEF(retval); + } + return false; + } + + return true; +} + +static bool zend_opcache_run_request_code(const char *code, const char *label, bool expect_single_shared_graph_ref, char *message, size_t message_size) +{ + zval retval; + + if (!zend_opcache_thread_request_startup()) { + snprintf(message, message_size, "%s: request startup failed", label); + return false; + } + + if (!zend_opcache_thread_eval(code, &retval, label)) { + snprintf(message, message_size, "%s: eval failed", label); + php_request_shutdown(NULL); + return false; + } + + if (!zend_is_true(&retval)) { + snprintf(message, message_size, "%s: returned false", label); + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return false; + } + + if (expect_single_shared_graph_ref && zend_opcache_static_cache_shared_graph_ref_count != 1) { + snprintf( + message, + message_size, + "%s: expected one request shared graph ref, got %u", + label, + zend_opcache_static_cache_shared_graph_ref_count + ); + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return false; + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return true; +} + +static bool zend_opcache_inspect_current_payload_state( + uint32_t *value_offset_out, + uint32_t *next_free_out, + int *refcount_out, + char *message, + size_t message_size) +{ + static const char key_name[] = "zts_request_shared_graph_refs_payload"; + zend_opcache_static_cache_context *previous_context; + zend_opcache_static_cache_header *header; + zend_opcache_static_cache_entry *entries, *entry = NULL; + const zend_opcache_static_cache_shared_graph_header *graph_header; + zend_string *key; + zend_ulong hash; + uint32_t index; + int state; + bool ok = false; + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + key = zend_string_init(key_name, sizeof(key_name) - 1, 0); + hash = zend_string_hash_val(key); + + if (!zend_opcache_static_cache_rlock()) { + snprintf(message, message_size, "unable to acquire volatile cache read lock"); + goto done; + } + + if (!zend_opcache_static_cache_header_is_initialized_locked()) { + snprintf(message, message_size, "volatile cache header is not initialized"); + zend_opcache_static_cache_unlock(); + goto done; + } + + header = zend_opcache_static_cache_header_ptr(); + entries = zend_opcache_static_cache_entries(header); + for (index = 0; index < header->capacity; index++) { + if (zend_opcache_static_cache_key_equals(&entries[index], key, hash)) { + entry = &entries[index]; + break; + } + } + + if (entry == NULL) { + snprintf(message, message_size, "volatile cache entry was not found after request shutdown"); + zend_opcache_static_cache_unlock(); + goto done; + } + + if (entry->value_type != ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { + snprintf(message, message_size, "unexpected cached value type %u", (unsigned int) entry->value_type); + zend_opcache_static_cache_unlock(); + goto done; + } + + graph_header = zend_opcache_locate_shared_graph_header(entry->value_offset); + if (graph_header == NULL) { + snprintf(message, message_size, "shared graph payload header is invalid"); + zend_opcache_static_cache_unlock(); + goto done; + } + + state = zend_atomic_int_load_ex(&graph_header->ref_state); + *value_offset_out = entry->value_offset; + *next_free_out = header->next_free; + *refcount_out = state & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_REFCOUNT_MASK; + + zend_opcache_static_cache_unlock(); + ok = true; + +done: + zend_string_release(key); + zend_opcache_static_cache_restore_context(previous_context); + return ok; +} + +static void *zend_opcache_ref_thread_main(void *arg) +{ + zend_opcache_ref_thread_ctx *ctx = (zend_opcache_ref_thread_ctx *) arg; + uint32_t expected_offset = 0; + uint32_t value_offset, next_free; + int refcount; + int iteration; + + ctx->result = 0; + ctx->message[0] = '\0'; + + if (!zend_opcache_run_request_code(clear_code, "zts shared graph clear", false, ctx->message, sizeof(ctx->message))) { + ctx->result = 1; + ts_free_thread(); + return NULL; + } + + for (iteration = 0; iteration < REQUEST_COUNT; iteration++) { + if (!zend_opcache_run_request_code(worker_code, "zts shared graph worker", true, ctx->message, sizeof(ctx->message))) { + ctx->result = 1; + break; + } + + if (!zend_opcache_inspect_current_payload_state(&value_offset, &next_free, &refcount, ctx->message, sizeof(ctx->message))) { + ctx->result = 1; + break; + } + + if (refcount != 0) { + ctx->result = 1; + snprintf( + ctx->message, + sizeof(ctx->message), + "shared graph refcount leaked after request %d (offset=%u next_free=%u refcount=%d)", + iteration + 1, + value_offset, + next_free, + refcount + ); + break; + } + + if (iteration == 0) { + expected_offset = value_offset; + } else if (value_offset != expected_offset) { + ctx->result = 1; + snprintf( + ctx->message, + sizeof(ctx->message), + "shared graph payload offset changed between requests (%u -> %u, next_free=%u)", + expected_offset, + value_offset, + next_free + ); + break; + } + } + + ts_free_thread(); + return NULL; +} + +int main(int argc, char **argv) +{ + zend_thread_t thread; + zend_opcache_ref_thread_ctx ctx; + + if (zend_opcache_test_startup(argc, argv) == FAILURE) { + fprintf(stderr, "startup failed\n"); + return 1; + } + + if (!zend_thread_start(&thread, zend_opcache_ref_thread_main, &ctx)) { + fprintf(stderr, "thread creation failed\n"); + zend_opcache_test_shutdown(); + return 1; + } + + if (!zend_thread_join(&thread)) { + fprintf(stderr, "thread join failed\n"); + zend_opcache_test_shutdown(); + return 1; + } + + if (ctx.result != 0) { + fprintf(stderr, "%s\n", ctx.message); + zend_opcache_test_shutdown(); + return 1; + } + + printf("ok\n"); + zend_opcache_test_shutdown(); + return 0; +} diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_001.c b/ext/opcache/tests/helpers/volatile_cache_zts_threads_001.c new file mode 100644 index 000000000000..7f7b74edc390 --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_001.c @@ -0,0 +1,281 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "php_config.h" +#endif + +#include +#include +#include +#include + +#include "Zend/zend_exceptions.h" +#include "Zend/zend_execute.h" +#include "Zend/zend_portability.h" +#include "sapi/embed/php_embed.h" + +#ifndef ZTS +# error "This helper requires a ZTS build" +#endif + +#define TEST_KEY "zts-thread-shared-key" +#define TEST_VALUE "writer-thread-payload-0123456789" + +static const char opcache_test_ini[] = + "html_errors=0\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n" + "opcache.enable=1\n" + "opcache.enable_cli=1\n" + "opcache.memory_consumption=64\n" + "opcache.max_accelerated_files=200\n" + "opcache.static_cache.volatile_size_mb=32\n\0"; + +typedef enum { + THREAD_MODE_STORE, + THREAD_MODE_FETCH +} zend_opcache_thread_mode; + +typedef struct _zend_opcache_thread_ctx { + zend_opcache_thread_mode mode; + int result; + char message[128]; +} zend_opcache_thread_ctx; + +static void zend_opcache_thread_set_failure(zend_opcache_thread_ctx *ctx, const char *message) +{ + ctx->result = 1; + snprintf(ctx->message, sizeof(ctx->message), "%s", message); +} + +static int zend_opcache_test_startup(int argc, char **argv) +{ + if (!php_tsrm_startup_ex(4)) { + return FAILURE; + } + ZEND_TSRMLS_CACHE_UPDATE(); + + zend_signal_startup(); + sapi_startup(&php_embed_module); + php_embed_module.ini_entries = opcache_test_ini; + if (argv != NULL) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module) == FAILURE) { + sapi_shutdown(); + tsrm_shutdown(); + return FAILURE; + } + + SG(options) |= SAPI_OPTION_NO_CHDIR; + return SUCCESS; +} + +static void zend_opcache_test_shutdown(void) +{ + php_module_shutdown(); + sapi_shutdown(); + tsrm_shutdown(); +} + +static bool zend_opcache_thread_request_startup(void) +{ + (void) ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); + + SG(request_info).argc = 0; + SG(request_info).argv = NULL; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = "-"; + SG(request_info).path_translated = NULL; + SG(request_info).request_method = "GET"; + SG(request_info).no_headers = 1; + + if (php_request_startup() == FAILURE) { + return false; + } + + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_register_variable("PHP_SELF", "-", NULL); + return true; +} + +static bool zend_opcache_thread_eval(const char *code, zval *retval, const char *label) +{ + ZVAL_UNDEF(retval); + if (zend_eval_stringl_ex(code, strlen(code), retval, label, true) == FAILURE) { + if (EG(exception)) { + zend_clear_exception(); + } + return false; + } + + if (EG(exception)) { + zend_clear_exception(); + if (!Z_ISUNDEF_P(retval)) { + zval_ptr_dtor(retval); + ZVAL_UNDEF(retval); + } + return false; + } + + return true; +} + +static void *zend_opcache_thread_main(void *arg) +{ + static const char store_code[] = + "OPcache\\volatile_store('" TEST_KEY "', '" TEST_VALUE "');"; + static const char fetch_code[] = + "OPcache\\volatile_fetch('" TEST_KEY "');"; + zend_opcache_thread_ctx *ctx; + zval retval; + bool request_started = false; + + ctx = (zend_opcache_thread_ctx *) arg; + ctx->result = 0; + ctx->message[0] = '\0'; + ZVAL_UNDEF(&retval); + + if (!zend_opcache_thread_request_startup()) { + zend_opcache_thread_set_failure(ctx, "request startup failed"); + goto cleanup; + } + request_started = true; + + if (ctx->mode == THREAD_MODE_STORE) { + if (!zend_opcache_thread_eval(store_code, &retval, "zts volatile cache store")) { + zend_opcache_thread_set_failure(ctx, "volatile_store failed"); + goto cleanup; + } + + if (!zend_is_true(&retval)) { + zend_opcache_thread_set_failure(ctx, "volatile_store returned false"); + } + } else { + if (!zend_opcache_thread_eval(fetch_code, &retval, "zts volatile cache fetch")) { + zend_opcache_thread_set_failure(ctx, "volatile_fetch failed"); + goto cleanup; + } + + if (Z_TYPE(retval) != IS_STRING || strcmp(Z_STRVAL(retval), TEST_VALUE) != 0) { + zend_opcache_thread_set_failure(ctx, "volatile_fetch returned unexpected value"); + } + } + +cleanup: + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + if (request_started) { + php_request_shutdown(NULL); + } + ts_free_thread(); + return NULL; +} + +int main(int argc, char **argv) +{ + static const char clear_code[] = "OPcache\\volatile_clear();"; + static const char fetch_code[] = + "OPcache\\volatile_fetch('" TEST_KEY "');"; + zend_thread_t writer_thread; + zend_thread_t reader_thread; + zend_opcache_thread_ctx writer_ctx; + zend_opcache_thread_ctx reader_ctx; + zval retval; + int exit_code; + + if (zend_opcache_test_startup(argc, argv) == FAILURE) { + fprintf(stderr, "startup failed\n"); + return 1; + } + + exit_code = 1; + if (!zend_opcache_thread_request_startup()) { + fprintf(stderr, "volatile_clear request startup failed\n"); + goto cleanup; + } + if (!zend_opcache_thread_eval(clear_code, &retval, "zts volatile cache clear")) { + fprintf(stderr, "volatile_clear failed\n"); + php_request_shutdown(NULL); + goto cleanup; + } + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + + writer_ctx.mode = THREAD_MODE_STORE; + if (!zend_thread_start(&writer_thread, zend_opcache_thread_main, &writer_ctx)) { + fprintf(stderr, "writer thread creation failed\n"); + goto cleanup; + } + if (!zend_thread_join(&writer_thread)) { + fprintf(stderr, "writer thread join failed\n"); + goto cleanup; + } + if (writer_ctx.result != 0) { + fprintf(stderr, "%s\n", writer_ctx.message); + goto cleanup; + } + + reader_ctx.mode = THREAD_MODE_FETCH; + if (!zend_thread_start(&reader_thread, zend_opcache_thread_main, &reader_ctx)) { + fprintf(stderr, "reader thread creation failed\n"); + goto cleanup; + } + if (!zend_thread_join(&reader_thread)) { + fprintf(stderr, "reader thread join failed\n"); + goto cleanup; + } + if (reader_ctx.result != 0) { + fprintf(stderr, "%s\n", reader_ctx.message); + goto cleanup; + } + + if (!zend_opcache_thread_request_startup()) { + fprintf(stderr, "main thread request startup failed\n"); + goto cleanup; + } + if (!zend_opcache_thread_eval(fetch_code, &retval, "zts volatile cache main fetch")) { + fprintf(stderr, "main thread fetch failed\n"); + php_request_shutdown(NULL); + goto cleanup; + } + if (Z_TYPE(retval) != IS_STRING || strcmp(Z_STRVAL(retval), TEST_VALUE) != 0) { + fprintf(stderr, "main thread fetch returned unexpected value\n"); + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + goto cleanup; + } + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + + printf("ok\n"); + exit_code = 0; + +cleanup: + zend_opcache_test_shutdown(); + return exit_code; +} diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_002.c b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002.c new file mode 100644 index 000000000000..09ca013893fc --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002.c @@ -0,0 +1,312 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "php_config.h" +#endif + +#include +#include +#include +#include + +#include "Zend/zend_exceptions.h" +#include "Zend/zend_execute.h" +#include "Zend/zend_portability.h" +#include "sapi/embed/php_embed.h" + +#ifndef ZTS +# error "This helper requires a ZTS build" +#endif + +typedef struct _zend_opcache_thread_ctx { + const char *mode; + const char *scenario_path; + const char *label; + int result; + char message[256]; +} zend_opcache_thread_ctx; + +static const char opcache_test_ini[] = + "html_errors=0\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n" + "opcache.enable=1\n" + "opcache.enable_cli=1\n" + "opcache.memory_consumption=64\n" + "opcache.max_accelerated_files=200\n" + "opcache.static_cache.volatile_size_mb=32\n\0"; + +static char *zend_opcache_build_scenario_code(const char *mode, const char *scenario_path); + +static void zend_opcache_thread_set_failure(zend_opcache_thread_ctx *ctx, const char *message) +{ + ctx->result = 1; + snprintf(ctx->message, sizeof(ctx->message), "%s", message); +} + +static int zend_opcache_test_startup(int argc, char **argv) +{ + if (!php_tsrm_startup_ex(4)) { + return FAILURE; + } + ZEND_TSRMLS_CACHE_UPDATE(); + + zend_signal_startup(); + sapi_startup(&php_embed_module); + php_embed_module.ini_entries = opcache_test_ini; + if (argv != NULL) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module) == FAILURE) { + sapi_shutdown(); + tsrm_shutdown(); + return FAILURE; + } + + SG(options) |= SAPI_OPTION_NO_CHDIR; + return SUCCESS; +} + +static void zend_opcache_test_shutdown(void) +{ + php_module_shutdown(); + sapi_shutdown(); + tsrm_shutdown(); +} + +static bool zend_opcache_thread_request_startup(void) +{ + (void) ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); + + SG(request_info).argc = 0; + SG(request_info).argv = NULL; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = "-"; + SG(request_info).path_translated = NULL; + SG(request_info).request_method = "GET"; + SG(request_info).no_headers = 1; + + if (php_request_startup() == FAILURE) { + return false; + } + + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_register_variable("PHP_SELF", "-", NULL); + return true; +} + +static bool zend_opcache_thread_eval(const char *code, zval *retval, const char *label) +{ + ZVAL_UNDEF(retval); + if (zend_eval_stringl_ex(code, strlen(code), retval, label, true) == FAILURE) { + if (EG(exception)) { + zend_clear_exception(); + } + return false; + } + + if (EG(exception)) { + zend_clear_exception(); + if (!Z_ISUNDEF_P(retval)) { + zval_ptr_dtor(retval); + ZVAL_UNDEF(retval); + } + return false; + } + + return true; +} + +static bool zend_opcache_thread_run_request_code(const char *code, const char *label, char *message, size_t message_size) +{ + zval retval; + + if (!zend_opcache_thread_request_startup()) { + snprintf(message, message_size, "%s: request startup failed", label); + return false; + } + + if (!zend_opcache_thread_eval(code, &retval, label)) { + snprintf(message, message_size, "%s: eval failed", label); + php_request_shutdown(NULL); + return false; + } + + if (!zend_is_true(&retval)) { + snprintf(message, message_size, "%s: returned false", label); + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return false; + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return true; +} + +static void *zend_opcache_thread_main(void *arg) +{ + char *code; + zend_opcache_thread_ctx *ctx; + + ctx = (zend_opcache_thread_ctx *) arg; + ctx->result = 0; + ctx->message[0] = '\0'; + + code = zend_opcache_build_scenario_code(ctx->mode, ctx->scenario_path); + if (code == NULL) { + zend_opcache_thread_set_failure(ctx, "failed to build scenario code"); + ts_free_thread(); + return NULL; + } + + if (!zend_opcache_thread_run_request_code(code, ctx->label, ctx->message, sizeof(ctx->message))) { + ctx->result = 1; + } + + free(code); + ts_free_thread(); + + return NULL; +} + +static bool zend_opcache_run_clear_request(char *message, size_t message_size) +{ + static const char clear_code[] = "OPcache\\volatile_clear(); return true;"; + zval retval; + + if (!zend_opcache_thread_request_startup()) { + snprintf(message, message_size, "zts volatile cache clear: request startup failed"); + return false; + } + + if (!zend_opcache_thread_eval(clear_code, &retval, "zts volatile cache clear")) { + snprintf(message, message_size, "zts volatile cache clear: eval failed"); + php_request_shutdown(NULL); + return false; + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return true; +} + +static char *zend_opcache_build_scenario_code(const char *mode, const char *scenario_path) +{ + char *code; + size_t code_len; + + code_len = strlen(mode) + strlen(scenario_path) + sizeof("$mode=''; require '';\0"); + code = malloc(code_len); + if (code == NULL) { + return NULL; + } + + snprintf(code, code_len, "$mode='%s'; require '%s';", mode, scenario_path); + return code; +} + +static bool zend_opcache_run_threaded_scenario(const char *scenario_path, char *message, size_t message_size) +{ + zend_thread_t store_thread; + zend_thread_t fetch_thread; + zend_opcache_thread_ctx store_ctx; + zend_opcache_thread_ctx fetch_ctx; + bool success; + + success = false; + + if (!zend_opcache_run_clear_request(message, message_size)) { + return false; + } + + store_ctx.mode = "store"; + store_ctx.scenario_path = scenario_path; + store_ctx.label = "zts threaded scenario store"; + if (!zend_thread_start(&store_thread, zend_opcache_thread_main, &store_ctx)) { + snprintf(message, message_size, "store thread creation failed"); + return false; + } + if (!zend_thread_join(&store_thread)) { + snprintf(message, message_size, "store thread join failed"); + return false; + } + if (store_ctx.result != 0) { + snprintf(message, message_size, "%s", store_ctx.message); + return false; + } + + fetch_ctx.mode = "fetch"; + fetch_ctx.scenario_path = scenario_path; + fetch_ctx.label = "zts threaded scenario fetch"; + if (!zend_thread_start(&fetch_thread, zend_opcache_thread_main, &fetch_ctx)) { + snprintf(message, message_size, "fetch thread creation failed"); + return false; + } + if (!zend_thread_join(&fetch_thread)) { + snprintf(message, message_size, "fetch thread join failed"); + return false; + } + if (fetch_ctx.result != 0) { + snprintf(message, message_size, "%s", fetch_ctx.message); + return false; + } + + success = true; + + return success; +} + +int main(int argc, char **argv) +{ + char message[256]; + int exit_code; + + if (argc < 2) { + fprintf(stderr, "scenario path required\n"); + return 1; + } + + if (zend_opcache_test_startup(argc, argv) == FAILURE) { + fprintf(stderr, "startup failed\n"); + return 1; + } + + exit_code = 1; + message[0] = '\0'; + + if (!zend_opcache_run_threaded_scenario(argv[1], message, sizeof(message))) { + fprintf(stderr, "%s\n", message); + goto cleanup; + } + + printf("ok\n"); + exit_code = 0; + +cleanup: + zend_opcache_test_shutdown(); + return exit_code; +} diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_materialization_nested.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_materialization_nested.inc new file mode 100644 index 000000000000..c3998a748b8b --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_materialization_nested.inc @@ -0,0 +1,58 @@ + $i, + 'text' => str_repeat(chr(65 + ($i % 26)), 96), + 'flags' => [$i, $i + 1, $i + 2, $i + 3], + ]; + } + + return $rows; +} + +if ($mode === 'store') { + return OPcache\volatile_store( + 'zts_materialization_nested_payload', + new WrappedPayload( + new LargePayload(build_rows(), 'nested'), + new DateTimeImmutable('2026-06-15 09:30:00', new DateTimeZone('UTC')), + ), + ); +} + +$before = memory_get_usage(); +$fetched = OPcache\volatile_fetch('zts_materialization_nested_payload'); + +if (!$fetched instanceof WrappedPayload) { + return false; +} + +$readOnly = $fetched->timestamp->format(DateTimeInterface::ATOM) . '|' . $fetched->payload->rows[100]['text']; +$afterFetch = memory_get_usage(); +$fetched->payload->rows[100]['text'] = 'changed'; +$afterMutate = memory_get_usage(); + +return $readOnly === '2026-06-15T09:30:00+00:00|' . str_repeat(chr(65 + (100 % 26)), 96) + && ($afterFetch - $before) < 262144 + && ($afterMutate - $afterFetch) > 131072; diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_materialization_plain.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_materialization_plain.inc new file mode 100644 index 000000000000..946b301a4d88 --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_materialization_plain.inc @@ -0,0 +1,44 @@ + $i, + 'text' => str_repeat(chr(65 + ($i % 26)), 96), + 'flags' => [$i, $i + 1, $i + 2, $i + 3], + ]; + } + + return $rows; +} + +if ($mode === 'store') { + return OPcache\volatile_store('zts_materialization_plain_payload', new LargePayload(build_rows(), 'plain')); +} + +$before = memory_get_usage(); +$fetched = OPcache\volatile_fetch('zts_materialization_plain_payload'); + +if (!$fetched instanceof LargePayload) { + return false; +} + +$readOnly = $fetched->rows[100]['text']; +$afterFetch = memory_get_usage(); +$fetched->rows[100]['text'] = 'changed'; +$afterMutate = memory_get_usage(); + +return $readOnly === str_repeat(chr(65 + (100 % 26)), 96) + && ($afterFetch - $before) < 262144 + && ($afterMutate - $afterFetch) > 131072; diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_safe_direct.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_safe_direct.inc new file mode 100644 index 000000000000..4fc5311773ea --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002_safe_direct.inc @@ -0,0 +1,110 @@ +label = $label; + $this->revision = $revision; + } + + public function describe(): string + { + return $this->label . ':' . $this->revision; + } +} + +class LabelIterator extends ArrayIterator +{ +} + +class TaggedCollection extends ArrayObject +{ + private string $type; + + public function __construct(array $data, string $type, string $iteratorClass) + { + parent::__construct($data, 0, $iteratorClass); + $this->type = $type; + } + + public function type(): string + { + return $this->type; + } +} + +class CustomSerializedDateTime extends DateTime +{ + public static int $unserializeCount = 0; + + private string $label; + + public function __construct(string $time, DateTimeZone $timezone, string $label) + { + parent::__construct($time, $timezone); + $this->label = $label; + } + + public function __serialize(): array + { + return parent::__serialize() + ['label' => $this->label]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCount++; + parent::__unserialize($data); + $this->label = $data['label']; + } + + public function label(): string + { + return $this->label; + } +} + +if ($mode === 'store') { + return OPcache\volatile_store( + 'zts_safe_direct_event_datetime', + new EventDateTime('2026-06-15 09:30:00.123456', new DateTimeZone('Europe/Paris'), 'launch', 7), + ) && OPcache\volatile_store( + 'zts_safe_direct_tagged_collection', + new TaggedCollection(['alpha' => 10, 'beta' => 20], 'metric', LabelIterator::class), + ) && OPcache\volatile_store( + 'zts_safe_direct_custom_datetime', + new CustomSerializedDateTime('2026-06-15 10:45:00.654321', new DateTimeZone('UTC'), 'fallback'), + ); +} + +$event = OPcache\volatile_fetch('zts_safe_direct_event_datetime'); +$collection = OPcache\volatile_fetch('zts_safe_direct_tagged_collection'); +$fallback = OPcache\volatile_fetch('zts_safe_direct_custom_datetime'); + +if (!$event instanceof EventDateTime) { + return false; +} + +if (!$collection instanceof TaggedCollection) { + return false; +} + +if (!$fallback instanceof CustomSerializedDateTime) { + return false; +} + +$iterator = $collection->getIterator(); + +return $event->format('Y-m-d H:i:s.u e') === '2026-06-15 09:30:00.123456 Europe/Paris' + && $event->describe() === 'launch:7' + && $collection->type() === 'metric' + && $iterator instanceof LabelIterator + && $collection['alpha'] === 10 + && $collection['beta'] === 20 + && $fallback->format('Y-m-d H:i:s.u e') === '2026-06-15 10:45:00.654321 UTC' + && $fallback->label() === 'fallback' + && CustomSerializedDateTime::$unserializeCount === 1; diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c new file mode 100644 index 000000000000..c5b1a800219c --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c @@ -0,0 +1,373 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "php_config.h" +#endif + +#include +#include +#include +#include + +#include "Zend/zend_exceptions.h" +#include "Zend/zend_execute.h" +#include "Zend/zend_portability.h" +#include "sapi/embed/php_embed.h" + +#ifndef ZTS +# error "This helper requires a ZTS build" +#endif + +typedef struct _zend_opcache_test_accel_directives { + zend_long memory_consumption; + zend_long static_cache_volatile_size_mb; + zend_long max_accelerated_files; + double max_wasted_percentage; + char *user_blacklist_filename; + zend_long force_restart_timeout; + bool use_cwd; + bool ignore_dups; + bool validate_timestamps; + bool revalidate_path; + bool save_comments; + bool record_warnings; + bool protect_memory; + bool file_override_enabled; + bool enable_cli; + bool validate_permission; +#ifndef ZEND_WIN32 + bool validate_root; +#endif + zend_ulong revalidate_freq; + zend_ulong file_update_protection; + char *error_log; +#ifdef ZEND_WIN32 + char *mmap_base; +#endif + char *memory_model; + zend_long log_verbosity_level; + zend_long optimization_level; + zend_long opt_debug_level; + zend_long max_file_size; + zend_long interned_strings_buffer; + char *restrict_api; +#ifndef ZEND_WIN32 + char *lockfile_path; +#endif + char *file_cache; + bool file_cache_read_only; + bool file_cache_only; + bool file_cache_consistency_checks; +#if ENABLE_FILE_CACHE_FALLBACK + bool file_cache_fallback; +#endif +#ifdef HAVE_HUGE_CODE_PAGES + bool huge_code_pages; +#endif + char *preload; +#ifndef ZEND_WIN32 + char *preload_user; +#endif +#ifdef ZEND_WIN32 + char *cache_id; +#endif +} zend_opcache_test_accel_directives; + +typedef struct _zend_opcache_test_globals { + bool counted; + bool enabled; + bool locked; + bool accelerator_enabled; + bool pcre_reseted; + zend_opcache_test_accel_directives accel_directives; +} zend_opcache_test_globals; + +typedef struct _zend_opcache_thread_ctx { + const char *mode; + const char *scenario_path; + const char *label; + int result; + char message[256]; +} zend_opcache_thread_ctx; + +extern size_t accel_globals_offset; + +#define ZEND_OPCACHE_TEST_CG(v) ZEND_TSRMG_FAST(accel_globals_offset, zend_opcache_test_globals *, v) + +static const char opcache_test_ini[] = + "html_errors=0\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n" + "opcache.enable=1\n" + "opcache.enable_cli=1\n" + "opcache.memory_consumption=64\n" + "opcache.max_accelerated_files=200\n" + "opcache.static_cache.volatile_size_mb=32\n\0"; + +static zend_opcache_test_accel_directives zend_opcache_thread_accel_directives; +static bool zend_opcache_thread_enabled; + +static char *zend_opcache_build_scenario_code(const char *mode, const char *scenario_path); + +static int zend_opcache_test_startup(int argc, char **argv) +{ + if (!php_tsrm_startup_ex(4)) { + return FAILURE; + } + ZEND_TSRMLS_CACHE_UPDATE(); + + zend_signal_startup(); + sapi_startup(&php_embed_module); + php_embed_module.ini_entries = opcache_test_ini; + if (argv != NULL) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module) == FAILURE) { + sapi_shutdown(); + tsrm_shutdown(); + return FAILURE; + } + + zend_opcache_thread_accel_directives = ZEND_OPCACHE_TEST_CG(accel_directives); + zend_opcache_thread_enabled = ZEND_OPCACHE_TEST_CG(enabled); + + SG(options) |= SAPI_OPTION_NO_CHDIR; + return SUCCESS; +} + +static void zend_opcache_test_shutdown(void) +{ + php_module_shutdown(); + sapi_shutdown(); + tsrm_shutdown(); +} + +static bool zend_opcache_thread_request_startup(void) +{ + (void) ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); + ZEND_OPCACHE_TEST_CG(accel_directives) = zend_opcache_thread_accel_directives; + ZEND_OPCACHE_TEST_CG(enabled) = zend_opcache_thread_enabled; + + SG(request_info).argc = 0; + SG(request_info).argv = NULL; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = "-"; + SG(request_info).path_translated = NULL; + SG(request_info).request_method = "GET"; + SG(request_info).no_headers = 1; + + if (php_request_startup() == FAILURE) { + return false; + } + + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_register_variable("PHP_SELF", "-", NULL); + return true; +} + +static bool zend_opcache_thread_eval(const char *code, zval *retval, const char *label) +{ + ZVAL_UNDEF(retval); + if (zend_eval_stringl_ex(code, strlen(code), retval, label, true) == FAILURE) { + if (EG(exception)) { + zend_clear_exception(); + } + return false; + } + + if (EG(exception)) { + zend_clear_exception(); + if (!Z_ISUNDEF_P(retval)) { + zval_ptr_dtor(retval); + ZVAL_UNDEF(retval); + } + return false; + } + + return true; +} + +static bool zend_opcache_thread_run_request_code(const char *code, const char *label, char *message, size_t message_size) +{ + zval retval; + + if (!zend_opcache_thread_request_startup()) { + snprintf(message, message_size, "%s: request startup failed", label); + return false; + } + + if (!zend_opcache_thread_eval(code, &retval, label)) { + snprintf(message, message_size, "%s: eval failed", label); + php_request_shutdown(NULL); + return false; + } + + if (!zend_is_true(&retval)) { + snprintf(message, message_size, "%s: returned false", label); + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return false; + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + php_request_shutdown(NULL); + return true; +} + +static char *zend_opcache_build_scenario_code(const char *mode, const char *scenario_path) +{ + char *code; + size_t code_len; + + code_len = strlen(mode) + strlen(scenario_path) + sizeof("$mode=''; require '';\0"); + code = malloc(code_len); + if (code == NULL) { + return NULL; + } + + snprintf(code, code_len, "$mode='%s'; require '%s';", mode, scenario_path); + return code; +} + +static void *zend_opcache_thread_main(void *arg) +{ + char *code; + zend_opcache_thread_ctx *ctx; + + ctx = (zend_opcache_thread_ctx *) arg; + ctx->result = 0; + ctx->message[0] = '\0'; + + code = zend_opcache_build_scenario_code(ctx->mode, ctx->scenario_path); + if (code == NULL) { + ctx->result = 1; + snprintf(ctx->message, sizeof(ctx->message), "failed to build scenario code"); + ts_free_thread(); + return NULL; + } + + if (!zend_opcache_thread_run_request_code(code, ctx->label, ctx->message, sizeof(ctx->message))) { + ctx->result = 1; + } + + free(code); + ts_free_thread(); + return NULL; +} + +static bool zend_opcache_run_mode_request(const char *mode, const char *scenario_path, const char *label, char *message, size_t message_size) +{ + char *code; + bool success; + + code = zend_opcache_build_scenario_code(mode, scenario_path); + if (code == NULL) { + snprintf(message, message_size, "%s: failed to build scenario code", label); + return false; + } + + success = zend_opcache_thread_run_request_code(code, label, message, message_size); + free(code); + return success; +} + +static bool zend_opcache_run_threaded_scenario(const char *scenario_path, char *message, size_t message_size) +{ + zend_thread_t reader_thread; + zend_thread_t writer_thread; + zend_opcache_thread_ctx reader_ctx; + zend_opcache_thread_ctx writer_ctx; + + if (!zend_opcache_run_mode_request("init", scenario_path, "zts lookup init", message, message_size)) { + return false; + } + + reader_ctx.mode = "reader"; + reader_ctx.scenario_path = scenario_path; + reader_ctx.label = "zts lookup reader"; + if (!zend_thread_start(&reader_thread, zend_opcache_thread_main, &reader_ctx)) { + snprintf(message, message_size, "reader thread creation failed"); + return false; + } + + writer_ctx.mode = "writer"; + writer_ctx.scenario_path = scenario_path; + writer_ctx.label = "zts lookup writer"; + if (!zend_thread_start(&writer_thread, zend_opcache_thread_main, &writer_ctx)) { + snprintf(message, message_size, "writer thread creation failed"); + zend_thread_join(&reader_thread); + return false; + } + + if (!zend_thread_join(&reader_thread)) { + snprintf(message, message_size, "reader thread join failed"); + zend_thread_join(&writer_thread); + return false; + } + if (!zend_thread_join(&writer_thread)) { + snprintf(message, message_size, "writer thread join failed"); + return false; + } + if (reader_ctx.result != 0) { + snprintf(message, message_size, "%s", reader_ctx.message); + return false; + } + if (writer_ctx.result != 0) { + snprintf(message, message_size, "%s", writer_ctx.message); + return false; + } + + return true; +} + +int main(int argc, char **argv) +{ + char message[256]; + int exit_code; + + if (argc < 2) { + fprintf(stderr, "scenario path required\n"); + return 1; + } + + if (zend_opcache_test_startup(argc, argv) == FAILURE) { + fprintf(stderr, "startup failed\n"); + return 1; + } + + exit_code = 1; + message[0] = '\0'; + + if (!zend_opcache_run_threaded_scenario(argv[1], message, sizeof(message))) { + fprintf(stderr, "%s\n", message); + goto cleanup; + } + + printf("ok\n"); + exit_code = 0; + +cleanup: + zend_opcache_test_shutdown(); + return exit_code; +} diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_clear.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_clear.inc new file mode 100644 index 000000000000..872c4d4832e1 --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_clear.inc @@ -0,0 +1,51 @@ += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +function cleanup_files(string ...$paths): void +{ + foreach ($paths as $path) { + @unlink($path); + } +} + +$key = 'zts_lookup_user_clear_key'; +$readyFile = sys_get_temp_dir() . '/opcache_zts_lookup_user_clear.ready'; +$doneFile = sys_get_temp_dir() . '/opcache_zts_lookup_user_clear.done'; + +if ($mode === 'init') { + cleanup_files($readyFile, $doneFile); + OPcache\volatile_clear(); + return OPcache\volatile_store($key, 'old'); +} + +if ($mode === 'writer') { + wait_for_file($readyFile); + OPcache\volatile_clear(); + file_put_contents($doneFile, 'done'); + return true; +} + +$first = fetch_or_miss($key); +file_put_contents($readyFile, 'ready'); +wait_for_file($doneFile); +$second = fetch_or_miss($key); +cleanup_files($readyFile, $doneFile); + +return $first === 'old' && $second === 'MISS'; diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_delete.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_delete.inc new file mode 100644 index 000000000000..5b078c4a9b9d --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_delete.inc @@ -0,0 +1,51 @@ += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +function cleanup_files(string ...$paths): void +{ + foreach ($paths as $path) { + @unlink($path); + } +} + +$key = 'zts_lookup_user_delete_key'; +$readyFile = sys_get_temp_dir() . '/opcache_zts_lookup_user_delete.ready'; +$doneFile = sys_get_temp_dir() . '/opcache_zts_lookup_user_delete.done'; + +if ($mode === 'init') { + cleanup_files($readyFile, $doneFile); + OPcache\volatile_clear(); + return OPcache\volatile_store($key, 'old'); +} + +if ($mode === 'writer') { + wait_for_file($readyFile); + OPcache\volatile_delete($key); + file_put_contents($doneFile, 'done'); + return true; +} + +$first = fetch_or_miss($key); +file_put_contents($readyFile, 'ready'); +wait_for_file($doneFile); +$second = fetch_or_miss($key); +cleanup_files($readyFile, $doneFile); + +return $first === 'old' && $second === 'MISS'; diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_hit.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_hit.inc new file mode 100644 index 000000000000..52f275d56123 --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_hit.inc @@ -0,0 +1,51 @@ += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +function cleanup_files(string ...$paths): void +{ + foreach ($paths as $path) { + @unlink($path); + } +} + +$key = 'zts_lookup_cache_hit_key'; +$readyFile = sys_get_temp_dir() . '/opcache_zts_lookup_cache_hit.ready'; +$doneFile = sys_get_temp_dir() . '/opcache_zts_lookup_cache_hit.done'; + +if ($mode === 'init') { + cleanup_files($readyFile, $doneFile); + OPcache\volatile_clear(); + return OPcache\volatile_store($key, 'old'); +} + +if ($mode === 'writer') { + wait_for_file($readyFile); + $result = OPcache\volatile_store($key, 'new'); + file_put_contents($doneFile, 'done'); + return $result; +} + +$first = fetch_or_miss($key); +file_put_contents($readyFile, 'ready'); +wait_for_file($doneFile); +$second = fetch_or_miss($key); +cleanup_files($readyFile, $doneFile); + +return $first === 'old' && $second === 'new'; diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_materialized_clear.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_materialized_clear.inc new file mode 100644 index 000000000000..35f93e67de14 --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_materialized_clear.inc @@ -0,0 +1,79 @@ + $i, + 'text' => str_repeat($prefix, 64), + 'nested' => ['value' => $i * $multiplier], + ]; + } + + return $rows; +} + +function wait_for_file(string $path): void +{ + $deadline = microtime(true) + 5.0; + + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +function cleanup_files(string ...$paths): void +{ + foreach ($paths as $path) { + @unlink($path); + } +} + +$key = 'zts_materialized_clear_payload'; +$overwriteKey = 'zts_materialized_clear_overwrite'; +$readyFile = sys_get_temp_dir() . '/opcache_zts_materialized_clear.ready'; +$doneFile = sys_get_temp_dir() . '/opcache_zts_materialized_clear.done'; + +if ($mode === 'init') { + cleanup_files($readyFile, $doneFile); + OPcache\volatile_clear(); + return OPcache\volatile_store($key, new ZtsClearPayload(build_zts_clear_rows('T', 5))); +} + +if ($mode === 'writer') { + wait_for_file($readyFile); + OPcache\volatile_clear(); + if (!OPcache\volatile_store($overwriteKey, new ZtsClearPayload(build_zts_clear_rows('X', 7)))) { + return false; + } + file_put_contents($doneFile, 'done'); + return true; +} + +$fetched = OPcache\volatile_fetch($key); +file_put_contents($readyFile, 'ready'); +wait_for_file($doneFile); +$before = $fetched->rows[123]['text']; +$fetched->rows[123]['text'] = 'changed after clear'; +$after = $fetched->rows[123]['text']; +$nested = $fetched->rows[123]['nested']['value']; + +$refetch = OPcache\volatile_fetch($key, 'MISS') === 'MISS' ? 'MISS' : 'HIT'; + +cleanup_files($readyFile, $doneFile); + +return $before === str_repeat('T', 64) + && $after === 'changed after clear' + && $nested === 615 + && $refetch === 'MISS'; diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_miss.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_miss.inc new file mode 100644 index 000000000000..313f22f4b382 --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_miss.inc @@ -0,0 +1,52 @@ += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +function cleanup_files(string ...$paths): void +{ + foreach ($paths as $path) { + @unlink($path); + } +} + +$key = 'zts_lookup_cache_miss_key'; +$readyFile = sys_get_temp_dir() . '/opcache_zts_lookup_cache_miss.ready'; +$doneFile = sys_get_temp_dir() . '/opcache_zts_lookup_cache_miss.done'; + +if ($mode === 'init') { + cleanup_files($readyFile, $doneFile); + OPcache\volatile_clear(); + OPcache\volatile_delete($key); + return true; +} + +if ($mode === 'writer') { + wait_for_file($readyFile); + $result = OPcache\volatile_store($key, 'created'); + file_put_contents($doneFile, 'done'); + return $result; +} + +$first = fetch_or_miss($key); +file_put_contents($readyFile, 'ready'); +wait_for_file($doneFile); +$second = fetch_or_miss($key); +cleanup_files($readyFile, $doneFile); + +return $first === 'MISS' && $second === 'created'; diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_volatile_static_class.inc b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_volatile_static_class.inc new file mode 100644 index 000000000000..a8703df93aba --- /dev/null +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003_volatile_static_class.inc @@ -0,0 +1,87 @@ + $i, + 'text' => str_repeat($kind, 64), + 'nested' => ['value' => $i * strlen($kind)], + ]; + } + + return [ + 'kind' => $kind, + 'rows' => $rows, + ]; +} + +function wait_for_file(string $path): void +{ + $deadline = microtime(true) + 5.0; + + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +function cleanup_files(string ...$paths): void +{ + foreach ($paths as $path) { + @unlink($path); + } +} + +$readyFile = sys_get_temp_dir() . '/opcache_zts_volatile_static_class.ready'; +$doneFile = sys_get_temp_dir() . '/opcache_zts_volatile_static_class.done'; + +if ($mode === 'init') { + cleanup_files($readyFile, $doneFile); + OPcache\volatile_clear(); + return true; +} + +if ($mode === 'writer') { + wait_for_file($readyFile); + $result = ZtsVolatileStaticClassPayload::resolve('writer'); + file_put_contents($doneFile, 'done'); + + return $result; +} + +$result = ZtsVolatileStaticClassPayload::resolve('reader'); +file_put_contents($readyFile, 'ready'); +wait_for_file($doneFile); +usleep(50000); + +for ($i = 0; $i < 100; $i++) { + $result = $result && ZtsVolatileStaticClassPayload::resolve('reader'); +} + +cleanup_files($readyFile, $doneFile); + +return $result; \ No newline at end of file diff --git a/ext/opcache/tests/static_cache_attribute_publish_userland_outside_lock_001.phpt b/ext/opcache/tests/static_cache_attribute_publish_userland_outside_lock_001.phpt new file mode 100644 index 000000000000..5871e3f946dd --- /dev/null +++ b/ext/opcache/tests/static_cache_attribute_publish_userland_outside_lock_001.phpt @@ -0,0 +1,63 @@ +--TEST-- +OPcache static attribute publish prepares userland-serialized values outside the cache write lock +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +backend, "\n"; + + if ($this->backend === 'volatile') { + OPcache\volatile_store('publish_inner_volatile', 'ok'); + } else { + OPcache\persistent_store('publish_inner_persistent', 'ok'); + } + + return ['backend' => $this->backend]; + } + + public function __unserialize(array $data): void + { + $this->backend = $data['backend']; + } +} + +class VolatilePublishTarget +{ + #[OPcache\VolatileStatic] + public static mixed $value; +} + +class PersistentPublishTarget +{ + #[OPcache\PersistentStatic] + public static mixed $value; +} + +OPcache\volatile_clear(); +VolatilePublishTarget::$value = new ReentrantPublishPayload('volatile'); +var_dump(OPcache\volatile_fetch('publish_inner_volatile')); + +OPcache\persistent_clear(); +PersistentPublishTarget::$value = new ReentrantPublishPayload('persistent'); +var_dump(OPcache\persistent_fetch('publish_inner_persistent')); + +?> +--EXPECT-- +serialize-volatile +string(2) "ok" +serialize-persistent +string(2) "ok" diff --git a/ext/opcache/tests/static_cache_attribute_signatures_001.phpt b/ext/opcache/tests/static_cache_attribute_signatures_001.phpt new file mode 100644 index 000000000000..7b3d7bff34b0 --- /dev/null +++ b/ext/opcache/tests/static_cache_attribute_signatures_001.phpt @@ -0,0 +1,50 @@ +--TEST-- +OPcache static cache attribute signatures expose TTL and strategy +--EXTENSIONS-- +opcache +--FILE-- +getName(); + } + + return 'complex'; +} + +$volatile = new ReflectionClass(OPcache\VolatileStatic::class); +$constructor = $volatile->getConstructor(); +foreach ($constructor->getParameters() as $parameter) { + $default = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null; + if ($default instanceof UnitEnum) { + $default = $default::class . '::' . $default->name; + } + echo $parameter->getName(), ':', describe_type($parameter->getType()), ':', var_export($default, true), "\n"; +} + +$attribute = new OPcache\VolatileStatic(5, OPcache\CacheStrategy::Tracking); +var_dump($attribute->ttl); +var_dump($attribute->strategy === OPcache\CacheStrategy::Tracking); + +try { + $attribute->ttl = 1; +} catch (Error $exception) { + echo "readonly-ttl\n"; +} + +var_dump((new ReflectionClass(OPcache\PersistentStatic::class))->getConstructor()); + +?> +--EXPECT-- +ttl:int:0 +strategy:OPcache\CacheStrategy:'OPcache\\CacheStrategy::Immediate' +int(5) +bool(true) +readonly-ttl +NULL diff --git a/ext/opcache/tests/static_cache_benchmark_scenarios_001.phpt b/ext/opcache/tests/static_cache_benchmark_scenarios_001.phpt new file mode 100644 index 000000000000..52da30a15e66 --- /dev/null +++ b/ext/opcache/tests/static_cache_benchmark_scenarios_001.phpt @@ -0,0 +1,23 @@ +--TEST-- +Benchmark scenario catalog exposes the vote-prep scenarios +--FILE-- + +--EXPECT-- +vote_read_long read 7 9 +carbon_datetime_compare read 2 9 +fetch_mutate_object read 1 3 +vote_write_throughput write 5 3 1 distinct 32 +vote_write_contention_shared write 5 3 5 shared 1 +vote_write_contention_distinct write 5 3 5 distinct 16 +vote_entry_reservation_contention write 2 3 5 shared 1 diff --git a/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt b/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt new file mode 100644 index 000000000000..665089635d89 --- /dev/null +++ b/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +OPcache __DirectCacheSafe does not touch internal classes when static cache memory is disabled +--EXTENSIONS-- +opcache +spl +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=0 +opcache.static_cache.persistent_size_mb=0 +--FILE-- +getAttributes(OPcache\__DirectCacheSafe::class))); +var_dump(count((new ReflectionClass(DateTimeImmutable::class))->getAttributes(OPcache\__DirectCacheSafe::class))); +var_dump(count((new ReflectionClass(SplFixedArray::class))->getAttributes(OPcache\__DirectCacheSafe::class))); +var_dump(count((new ReflectionClass(ArrayObject::class))->getAttributes(OPcache\__DirectCacheSafe::class))); + +?> +--EXPECT-- +int(0) +int(0) +int(0) +int(0) diff --git a/ext/opcache/tests/static_cache_explicit_cache_complex_store_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_complex_store_001.phpt new file mode 100644 index 000000000000..127af903b27e --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_complex_store_001.phpt @@ -0,0 +1,104 @@ +--TEST-- +OPcache explicit cache stores handle mixed complex payloads +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + $this->id, 'name' => $this->name]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCount++; + $this->id = $data['id']; + $this->name = $data['name']; + } + + public function info(): string + { + return $this->id . ':' . $this->name; + } +} + +$routePayload = [ + 'nested' => ['alpha' => [1, 2, 3], 'beta' => ['x' => 'y']], + 'headers' => ['cache-control' => 'public, max-age=60'], +]; + +var_dump(OPcache\volatile_store_array([ + 'route' => $routePayload, + 'meta' => new ExplicitPreparedUser('Alice', 30), + 'serial' => new ExplicitPreparedSerializableUser(7, 'Bob'), + 'internal' => new DateTimeImmutable('2026-06-15 09:30:00', new DateTimeZone('UTC')), +])); + +$route = OPcache\volatile_fetch('route'); +$meta = OPcache\volatile_fetch('meta'); +$serial = OPcache\volatile_fetch('serial'); +$internal = OPcache\volatile_fetch('internal'); + +var_dump($route['nested']['alpha'][2]); +var_dump($meta instanceof ExplicitPreparedUser); +var_dump($meta->name); +var_dump($meta->age); +var_dump($serial instanceof ExplicitPreparedSerializableUser); +var_dump($serial->info()); +var_dump(ExplicitPreparedSerializableUser::$serializeCount); +var_dump(ExplicitPreparedSerializableUser::$unserializeCount); +var_dump($internal instanceof DateTimeImmutable); +var_dump($internal->format('Y-m-d H:i:s')); + +OPcache\persistent_store('persistent_user', new ExplicitPreparedUser('Carol', 40)); +$persistent = OPcache\persistent_fetch('persistent_user'); + +var_dump($persistent instanceof ExplicitPreparedUser); +var_dump($persistent->name); +var_dump($persistent->age); + +var_dump(OPcache\volatile_store_array([])); + +?> +--EXPECT-- +bool(true) +int(3) +bool(true) +string(5) "Alice" +int(30) +bool(true) +string(5) "7:Bob" +int(1) +int(1) +bool(true) +string(19) "2026-06-15 09:30:00" +bool(true) +string(5) "Carol" +int(40) +bool(true) diff --git a/ext/opcache/tests/static_cache_explicit_cache_destructive_mutators_no_deadlock_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_destructive_mutators_no_deadlock_001.phpt new file mode 100644 index 000000000000..1a4dfc3f16b5 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_destructive_mutators_no_deadlock_001.phpt @@ -0,0 +1,233 @@ +--TEST-- +OPcache explicit cache destructive mutators do not wait on reservation locks +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + usleep(1000); + } +} + +function wait_for_result(string $path, float $seconds): bool +{ + $deadline = microtime(true) + $seconds; + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + return false; + } + usleep(1000); + } + + return true; +} + +function cache_clear(string $backend): void +{ + if ($backend === 'volatile') { + OPcache\volatile_clear(); + } else { + OPcache\persistent_clear(); + } +} + +function cache_store(string $backend, string $key, mixed $value): void +{ + if ($backend === 'volatile') { + OPcache\volatile_store($key, $value); + } else { + OPcache\persistent_store($key, $value); + } +} + +function cache_fetch(string $backend, string $key, mixed $default = null): mixed +{ + return $backend === 'volatile' + ? OPcache\volatile_fetch($key, $default) + : OPcache\persistent_fetch($key, $default); +} + +function cache_lock(string $backend, string $key): bool +{ + return $backend === 'volatile' + ? OPcache\volatile_lock($key) + : OPcache\persistent_lock($key); +} + +function cache_delete(string $backend, string $key): void +{ + if ($backend === 'volatile') { + OPcache\volatile_delete($key); + } else { + OPcache\persistent_delete($key); + } +} + +function run_reservation_cycle(string $backend, string $label, callable $operation): void +{ + $prefix = sys_get_temp_dir() . '/opcache_destructive_mutator_no_deadlock_' . getmypid() . '_' . $backend . '_' . $label; + $readyFile = $prefix . '.ready'; + $operationFile = $prefix . '.operation'; + $deleteFile = $prefix . '.delete'; + @unlink($readyFile); + @unlink($operationFile); + @unlink($deleteFile); + + $reservedKey = key_for_stripe($prefix . ':reserved', 200); + $blockedKey = key_for_stripe($prefix . ':blocked', 1); + + cache_clear($backend); + if (!cache_lock($backend, $reservedKey)) { + throw new RuntimeException("failed to reserve {$backend} key"); + } + + $operationPid = pcntl_fork(); + if ($operationPid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($operationPid === 0) { + file_put_contents($readyFile, 'ready'); + $operation(); + file_put_contents($operationFile, "{$label}: done\n"); + exit(0); + } + + wait_for_file($readyFile); + usleep(200000); + + $deletePid = pcntl_fork(); + if ($deletePid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($deletePid === 0) { + cache_delete($backend, $blockedKey); + file_put_contents($deleteFile, 'delete-low: done'); + exit(0); + } + + $operationDone = wait_for_result($operationFile, 2.0); + $deleteDone = wait_for_result($deleteFile, 2.0); + if (!$operationDone || !$deleteDone) { + echo "{$backend} {$label}: timeout\n"; + } + + cache_store($backend, $reservedKey, 'release'); + pcntl_waitpid($operationPid, $status); + pcntl_waitpid($deletePid, $status); + + if ($operationDone && $deleteDone) { + echo "{$backend} {$label}: no deadlock\n"; + } + + @unlink($readyFile); + @unlink($operationFile); + @unlink($deleteFile); +} + +function run_delete_wait_cycle(string $backend): void +{ + $prefix = sys_get_temp_dir() . '/opcache_destructive_mutator_no_deadlock_' . getmypid() . '_' . $backend . '_delete'; + $resultFile = $prefix . '.result'; + @unlink($resultFile); + + $key = key_for_stripe($prefix . ':reserved-delete', 17); + + cache_clear($backend); + cache_store($backend, $key, 'value'); + if (!cache_lock($backend, $key)) { + throw new RuntimeException("failed to reserve {$backend} delete key"); + } + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + cache_delete($backend, $key); + file_put_contents($resultFile, 'delete: done'); + exit(0); + } + + $deleteDone = wait_for_result($resultFile, 2.0); + if (!$deleteDone) { + echo "{$backend} delete: timeout\n"; + } + + if ($deleteDone) { + echo "{$backend} delete: no wait\n"; + echo "{$backend} delete fetch: ", cache_fetch($backend, $key, 'MISS'), "\n"; + } + + cache_store($backend, $key, 'release'); + pcntl_waitpid($pid, $status); + + @unlink($resultFile); +} + +run_reservation_cycle('volatile', 'clear', static fn () => cache_clear('volatile')); +run_reservation_cycle('persistent', 'clear', static fn () => cache_clear('persistent')); +run_reservation_cycle('volatile', 'reset', static fn () => opcache_reset()); +run_delete_wait_cycle('volatile'); +run_delete_wait_cycle('persistent'); + +?> +--EXPECT-- +volatile clear: no deadlock +persistent clear: no deadlock +volatile reset: no deadlock +volatile delete: no wait +volatile delete fetch: MISS +persistent delete: no wait +persistent delete fetch: MISS +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_explicit_cache_fetch_array_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_fetch_array_001.phpt new file mode 100644 index 000000000000..280acdbee447 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_fetch_array_001.phpt @@ -0,0 +1,115 @@ +--TEST-- +OPcache explicit fetch_array APIs handle hits, missing keys, and default values +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + 'value', + 'null' => null, + ]); + cache_store($backend, '42', 'numeric'); + + var_dump(cache_fetch_array($backend, ['hit', 'missing', 'null'])); + var_dump(cache_fetch_array($backend, ['hit', 'missing', 'null'], ['fallback'])); + var_dump(cache_fetch_array($backend, [42])); + var_dump(cache_fetch_array($backend, [])); +} + +?> +--EXPECT-- +volatile +array(3) { + ["hit"]=> + string(5) "value" + ["missing"]=> + NULL + ["null"]=> + NULL +} +array(3) { + ["hit"]=> + string(5) "value" + ["missing"]=> + array(1) { + [0]=> + string(8) "fallback" + } + ["null"]=> + NULL +} +array(1) { + ["42"]=> + string(7) "numeric" +} +array(0) { +} +persistent +array(3) { + ["hit"]=> + string(5) "value" + ["missing"]=> + NULL + ["null"]=> + NULL +} +array(3) { + ["hit"]=> + string(5) "value" + ["missing"]=> + array(1) { + [0]=> + string(8) "fallback" + } + ["null"]=> + NULL +} +array(1) { + ["42"]=> + string(7) "numeric" +} +array(0) { +} diff --git a/ext/opcache/tests/static_cache_explicit_cache_fetch_userland_outside_lock_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_fetch_userland_outside_lock_001.phpt new file mode 100644 index 000000000000..bfaee7454dc2 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_fetch_userland_outside_lock_001.phpt @@ -0,0 +1,62 @@ +--TEST-- +OPcache explicit cache fetch materializes userland objects outside the cache read lock +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + $this->backend]; + } + + public function __unserialize(array $data): void + { + $this->backend = $data['backend']; + echo "unserialize-", $this->backend, "\n"; + + if ($this->backend === 'volatile') { + OPcache\volatile_store('fetch_inner_volatile', 'ok'); + } else { + OPcache\persistent_store('fetch_inner_persistent', 'ok'); + } + } +} + +foreach (['volatile', 'persistent'] as $backend) { + echo $backend, "\n"; + + if ($backend === 'volatile') { + OPcache\volatile_clear(); + OPcache\volatile_store('fetch_payload', new ReentrantFetchPayload($backend)); + var_dump(OPcache\volatile_fetch('fetch_payload') instanceof ReentrantFetchPayload); + var_dump(OPcache\volatile_fetch('fetch_inner_volatile')); + } else { + OPcache\persistent_clear(); + OPcache\persistent_store('fetch_payload', new ReentrantFetchPayload($backend)); + var_dump(OPcache\persistent_fetch('fetch_payload') instanceof ReentrantFetchPayload); + var_dump(OPcache\persistent_fetch('fetch_inner_persistent')); + } +} + +?> +--EXPECT-- +volatile +unserialize-volatile +bool(true) +string(2) "ok" +persistent +unserialize-persistent +bool(true) +string(2) "ok" diff --git a/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_001.phpt new file mode 100644 index 000000000000..36b3dc103ec2 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_001.phpt @@ -0,0 +1,93 @@ +--TEST-- +OPcache explicit volatile and persistent caches relocate fragmented payload blocks before store failure +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +opcache.static_cache.persistent_size_mb=8 +--FILE-- + +--EXPECT-- +-- volatile -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +int(2400000) +int(1200000) +int(1200000) +int(1200000) +-- persistent -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +int(2400000) +int(1200000) +int(1200000) +int(1200000) diff --git a/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt new file mode 100644 index 000000000000..75d316599f01 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt @@ -0,0 +1,107 @@ +--TEST-- +OPcache explicit caches skip relocation when fragmented free memory still cannot satisfy the store +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +opcache.static_cache.persistent_size_mb=8 +--FILE-- +getMessage(), "\n"; + } + + var_dump(cache_fetch($kind, $prefix . 'probe') === $probe); + var_dump(strlen(cache_fetch($kind, $prefix . 'first'))); + var_dump(strlen(cache_fetch($kind, $prefix . 'third'))); + var_dump(strlen(cache_fetch($kind, $prefix . 'fourth'))); +} + +run_relocation_skip('volatile'); +run_relocation_skip('persistent'); + +?> +--EXPECT-- +-- volatile -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +int(1200000) +int(1200000) +int(1200000) +-- persistent -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +OPcache\StaticCacheException: not enough shared memory left +bool(false) +int(1200000) +int(1200000) +int(1200000) diff --git a/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt new file mode 100644 index 000000000000..6f15683579a0 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt @@ -0,0 +1,80 @@ +--TEST-- +OPcache explicit cache APIs reject empty and non-scalar cache keys +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +getMessage(), "\n"; + } +} + +class StringableKey +{ + public function __toString(): string + { + echo "__toString called\n"; + + return "stringable"; + } +} + +foreach (['volatile', 'persistent'] as $backend) { + echo $backend, "\n"; + + $store = $backend === 'volatile' ? OPcache\volatile_store(...) : OPcache\persistent_store(...); + $storeArray = $backend === 'volatile' ? OPcache\volatile_store_array(...) : OPcache\persistent_store_array(...); + $fetch = $backend === 'volatile' ? OPcache\volatile_fetch(...) : OPcache\persistent_fetch(...); + $fetchArray = $backend === 'volatile' ? OPcache\volatile_fetch_array(...) : OPcache\persistent_fetch_array(...); + $exists = $backend === 'volatile' ? OPcache\volatile_exists(...) : OPcache\persistent_exists(...); + $lock = $backend === 'volatile' ? OPcache\volatile_lock(...) : OPcache\persistent_lock(...); + $delete = $backend === 'volatile' ? OPcache\volatile_delete(...) : OPcache\persistent_delete(...); + $deleteArray = $backend === 'volatile' ? OPcache\volatile_delete_array(...) : OPcache\persistent_delete_array(...); + + dump_error('store-empty', static fn () => $store('', 'value')); + dump_error('store-array-empty', static fn () => $storeArray(['' => 'value'])); + dump_error('fetch-empty', static fn () => $fetch('')); + dump_error('fetch-array-empty', static fn () => $fetchArray([''])); + dump_error('fetch-array-object', static fn () => $fetchArray([new StringableKey()])); + dump_error('exists-empty', static fn () => $exists('')); + dump_error('lock-empty', static fn () => $lock('')); + dump_error('delete-empty', static fn () => $delete('')); + dump_error('delete-array-empty', static fn () => $deleteArray([''])); + dump_error('delete-array-object', static fn () => $deleteArray([new StringableKey()])); +} + +?> +--EXPECTF-- +volatile +store-empty: ValueError: OPcache\volatile_store(): Argument #1 ($key) must be a non-empty string +store-array-empty: ValueError: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys +fetch-empty: ValueError: OPcache\volatile_fetch(): Argument #1 ($key) must be a non-empty string +fetch-array-empty: ValueError: OPcache\volatile_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +fetch-array-object: ValueError: OPcache\volatile_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +exists-empty: ValueError: OPcache\volatile_exists(): Argument #1 ($key) must be a non-empty string +lock-empty: ValueError: OPcache\volatile_lock(): Argument #1 ($key) must be a non-empty string +delete-empty: ValueError: OPcache\volatile_delete(): Argument #1 ($key) must be a non-empty string +delete-array-empty: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +delete-array-object: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +persistent +store-empty: ValueError: OPcache\persistent_store(): Argument #1 ($key) must be a non-empty string +store-array-empty: ValueError: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys +fetch-empty: ValueError: OPcache\persistent_fetch(): Argument #1 ($key) must be a non-empty string +fetch-array-empty: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +fetch-array-object: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +exists-empty: ValueError: OPcache\persistent_exists(): Argument #1 ($key) must be a non-empty string +lock-empty: ValueError: OPcache\persistent_lock(): Argument #1 ($key) must be a non-empty string +delete-empty: ValueError: OPcache\persistent_delete(): Argument #1 ($key) must be a non-empty string +delete-array-empty: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +delete-array-object: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_clear_reset_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_clear_reset_001.phpt new file mode 100644 index 000000000000..307425c6ffc3 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_clear_reset_001.phpt @@ -0,0 +1,73 @@ +--TEST-- +OPcache explicit cache clear and reset release cache locks +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + +--EXPECT-- +volatile +bool(true) +bool(true) +bool(true) +persistent +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_fork_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_fork_001.phpt new file mode 100644 index 000000000000..dd9f5121d578 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_fork_001.phpt @@ -0,0 +1,202 @@ +--TEST-- +OPcache explicit cache locks are non-blocking for locked keys +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + usleep(1000); + } +} + +function cache_clear(string $backend): void +{ + if ($backend === 'volatile') { + OPcache\volatile_clear(); + } else { + OPcache\persistent_clear(); + } +} + +function cache_store(string $backend, string $key, mixed $value): mixed +{ + if ($backend === 'volatile') { + return OPcache\volatile_store($key, $value); + } + + OPcache\persistent_store($key, $value); + return null; +} + +function cache_fetch(string $backend, string $key, mixed $default = null): mixed +{ + return $backend === 'volatile' + ? OPcache\volatile_fetch($key, $default) + : OPcache\persistent_fetch($key, $default); +} + +function cache_exists(string $backend, string $key): bool +{ + return $backend === 'volatile' + ? OPcache\volatile_exists($key) + : OPcache\persistent_exists($key); +} + +function cache_lock(string $backend, string $key): bool +{ + return $backend === 'volatile' + ? OPcache\volatile_lock($key) + : OPcache\persistent_lock($key); +} + +function run_child(string $backend, string $label, callable $operation): string +{ + $prefix = sys_get_temp_dir() . '/opcache_explicit_exists_lock_fork_' . getmypid() . '_' . $backend . '_' . $label; + $readyFile = $prefix . '.ready'; + $resultFile = $prefix . '.result'; + @unlink($readyFile); + @unlink($resultFile); + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + pcntl_async_signals(true); + pcntl_signal(SIGALRM, static function () use ($resultFile): void { + file_put_contents($resultFile, "timeout\n"); + exit(1); + }); + pcntl_alarm(3); + file_put_contents($readyFile, 'ready'); + file_put_contents($resultFile, $operation()); + pcntl_alarm(0); + exit(0); + } + + wait_for_file($readyFile); + usleep(100000); + + return $pid . ':' . $resultFile . ':' . $readyFile; +} + +function finish_child(string $child): string +{ + [$pid, $resultFile, $readyFile] = explode(':', $child); + pcntl_waitpid((int) $pid, $status); + $result = file_get_contents($resultFile); + @unlink($readyFile); + @unlink($resultFile); + + return $result; +} + +function run_missing_lock_returns_false_when_locked(string $backend): void +{ + $key = $backend . '_missing_lock_blocks_' . getmypid(); + + cache_clear($backend); + var_dump(cache_lock($backend, $key)); + + $child = run_child($backend, 'missing_lock', static function () use ($backend, $key): string { + $locked = cache_lock($backend, $key); + $exists = cache_exists($backend, $key); + $value = cache_fetch($backend, $key, 'MISS'); + + return ($locked ? 'locked' : 'not locked') . "\n" . ($exists ? 'true' : 'false') . "\n" . $value . "\n"; + }); + + [, $resultFile] = explode(':', $child); + echo 'missing-lock blocked: ', file_exists($resultFile) ? 'no' : 'yes', "\n"; + cache_store($backend, $key, 'built'); + echo finish_child($child); +} + +function run_missing_without_lock_does_not_reserve(string $backend): void +{ + $key = $backend . '_missing_without_lock_' . getmypid(); + + cache_clear($backend); + var_dump(cache_exists($backend, $key)); + + $child = run_child($backend, 'missing_no_lock', static function () use ($backend, $key): string { + cache_store($backend, $key, 'child'); + + return cache_fetch($backend, $key, 'MISS') . "\n"; + }); + + echo finish_child($child); +} + +function run_existing_exists_does_not_reserve(string $backend): void +{ + $key = $backend . '_existing_lock_' . getmypid(); + + cache_clear($backend); + cache_store($backend, $key, 'owner'); + var_dump(cache_exists($backend, $key)); + + $child = run_child($backend, 'existing_lock', static function () use ($backend, $key): string { + cache_store($backend, $key, 'child'); + + return cache_fetch($backend, $key, 'MISS') . "\n"; + }); + + echo finish_child($child); +} + +foreach (['volatile', 'persistent'] as $backend) { + echo $backend, "\n"; + run_missing_lock_returns_false_when_locked($backend); + run_missing_without_lock_does_not_reserve($backend); + run_existing_exists_does_not_reserve($backend); +} + +?> +--EXPECT-- +volatile +bool(true) +missing-lock blocked: no +not locked +false +MISS +bool(false) +child +bool(true) +child +persistent +bool(true) +missing-lock blocked: no +not locked +false +MISS +bool(false) +child +bool(true) +child +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_parent_survives_child_shutdown_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_parent_survives_child_shutdown_001.phpt new file mode 100644 index 000000000000..398b62680da1 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_parent_survives_child_shutdown_001.phpt @@ -0,0 +1,96 @@ +--TEST-- +OPcache explicit cache parent locks survive forked child shutdown +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + +--EXPECT-- +volatile +bool(true) +not locked +not locked +locked +persistent +bool(true) +not locked +not locked +locked +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_public_mutators_fork_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_public_mutators_fork_001.phpt new file mode 100644 index 000000000000..9c14aa053086 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_public_mutators_fork_001.phpt @@ -0,0 +1,291 @@ +--TEST-- +OPcache explicit cache locks make builders wait while destructive mutators bypass reservations +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + usleep(1000); + } +} + +function result_to_string(mixed $value): string +{ + return match (true) { + $value === null => 'null', + is_bool($value) => $value ? 'true' : 'false', + default => (string) $value, + }; +} + +function cache_clear(string $backend): void +{ + if ($backend === 'volatile') { + OPcache\volatile_clear(); + } else { + OPcache\persistent_clear(); + } +} + +function cache_store(string $backend, string $key, mixed $value): mixed +{ + if ($backend === 'volatile') { + return OPcache\volatile_store($key, $value); + } + + OPcache\persistent_store($key, $value); + return null; +} + +function cache_store_array(string $backend, array $values): mixed +{ + if ($backend === 'volatile') { + return OPcache\volatile_store_array($values); + } + + OPcache\persistent_store_array($values); + return null; +} + +function cache_fetch(string $backend, string $key, mixed $default = null): mixed +{ + return $backend === 'volatile' + ? OPcache\volatile_fetch($key, $default) + : OPcache\persistent_fetch($key, $default); +} + +function cache_lock(string $backend, string $key): bool +{ + return $backend === 'volatile' + ? OPcache\volatile_lock($key) + : OPcache\persistent_lock($key); +} + +function cache_delete(string $backend, string $key): void +{ + if ($backend === 'volatile') { + OPcache\volatile_delete($key); + } else { + OPcache\persistent_delete($key); + } +} + +function cache_delete_array(string $backend, array $keys): void +{ + if ($backend === 'volatile') { + OPcache\volatile_delete_array($keys); + } else { + OPcache\persistent_delete_array($keys); + } +} + +function run_blocked_mutator(string $backend, string $label, callable $operation, callable $release, callable $after): void +{ + $prefix = sys_get_temp_dir() . '/opcache_explicit_exists_lock_public_mutators_' . getmypid() . '_' . $backend . '_' . $label; + $readyFile = $prefix . '.ready'; + $resultFile = $prefix . '.result'; + @unlink($readyFile); + @unlink($resultFile); + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + pcntl_async_signals(true); + pcntl_signal(SIGALRM, static function () use ($resultFile): void { + file_put_contents($resultFile, "timeout\n"); + exit(1); + }); + pcntl_alarm(3); + file_put_contents($readyFile, 'ready'); + $result = $operation(); + pcntl_alarm(0); + file_put_contents($resultFile, $label . ' result: ' . result_to_string($result) . "\n"); + exit(0); + } + + wait_for_file($readyFile); + usleep(200000); + echo $label, ' blocked: ', file_exists($resultFile) ? 'no' : 'yes', "\n"; + $release(); + pcntl_waitpid($pid, $status); + echo file_get_contents($resultFile); + $after(); + + @unlink($readyFile); + @unlink($resultFile); +} + +function reserve_missing(string $backend, string $key): void +{ + cache_clear($backend); + var_dump(cache_lock($backend, $key)); +} + +function run_backend_mutators(string $backend): void +{ + $baseKey = $backend . '_exists_lock_public_mutators_' . getmypid(); + + echo $backend, "\n"; + + $key = $baseKey . '_store'; + reserve_missing($backend, $key); + run_blocked_mutator( + $backend, + 'store', + static fn () => cache_store($backend, $key, 'child'), + static fn () => cache_store($backend, $key, 'owner'), + static fn () => print 'store value: ' . cache_fetch($backend, $key, 'MISS') . "\n", + ); + + $key = $baseKey . '_store_array'; + reserve_missing($backend, $key); + run_blocked_mutator( + $backend, + 'store_array', + static fn () => cache_store_array($backend, [$key => 'child']), + static fn () => cache_store($backend, $key, 'owner'), + static fn () => print 'store_array value: ' . cache_fetch($backend, $key, 'MISS') . "\n", + ); + + $key = $baseKey . '_delete'; + reserve_missing($backend, $key); + run_blocked_mutator( + $backend, + 'delete', + static fn () => cache_delete($backend, $key), + static fn () => cache_store($backend, $key, 'owner'), + static fn () => print 'delete value: ' . cache_fetch($backend, $key, 'MISS') . "\n", + ); + + $key = $baseKey . '_delete_array'; + reserve_missing($backend, $key); + run_blocked_mutator( + $backend, + 'delete_array', + static fn () => cache_delete_array($backend, [$key]), + static fn () => cache_store($backend, $key, 'owner'), + static fn () => print 'delete_array value: ' . cache_fetch($backend, $key, 'MISS') . "\n", + ); + + if ($backend === 'persistent') { + $key = $baseKey . '_atomic'; + reserve_missing($backend, $key); + run_blocked_mutator( + $backend, + 'atomic', + static fn () => OPcache\persistent_atomic_increment($key, 2), + static fn () => cache_store($backend, $key, 10), + static fn () => print 'atomic value: ' . cache_fetch($backend, $key, 'MISS') . "\n", + ); + + $key = $baseKey . '_atomic_decrement'; + reserve_missing($backend, $key); + run_blocked_mutator( + $backend, + 'atomic_decrement', + static fn () => OPcache\persistent_atomic_decrement($key, 3), + static fn () => cache_store($backend, $key, 10), + static fn () => print 'atomic_decrement value: ' . cache_fetch($backend, $key, 'MISS') . "\n", + ); + } + + $key = $baseKey . '_clear'; + $survivorKey = $baseKey . '_clear_survivor'; + cache_clear($backend); + cache_store($backend, $survivorKey, 'survives'); + echo "stored survivor\n"; + var_dump(cache_lock($backend, $key)); + run_blocked_mutator( + $backend, + 'clear', + static fn () => cache_clear($backend), + static fn () => cache_store($backend, $key, 'owner'), + static fn () => print 'clear values: ' . cache_fetch($backend, $key, 'MISS') . ',' . cache_fetch($backend, $survivorKey, 'MISS') . "\n", + ); +} + +run_backend_mutators('volatile'); +run_backend_mutators('persistent'); + +?> +--EXPECT-- +volatile +bool(true) +store blocked: yes +store result: true +store value: child +bool(true) +store_array blocked: yes +store_array result: true +store_array value: child +bool(true) +delete blocked: no +delete result: null +delete value: owner +bool(true) +delete_array blocked: no +delete_array result: null +delete_array value: owner +stored survivor +bool(true) +clear blocked: no +clear result: null +clear values: owner,MISS +persistent +bool(true) +store blocked: yes +store result: null +store value: child +bool(true) +store_array blocked: yes +store_array result: null +store_array value: child +bool(true) +delete blocked: no +delete result: null +delete value: owner +bool(true) +delete_array blocked: no +delete_array result: null +delete_array value: owner +bool(true) +atomic blocked: yes +atomic result: 12 +atomic value: 12 +bool(true) +atomic_decrement blocked: yes +atomic_decrement result: 7 +atomic_decrement value: 7 +stored survivor +bool(true) +clear blocked: no +clear result: null +clear values: owner,MISS +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_rshutdown_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_rshutdown_001.phpt new file mode 100644 index 000000000000..b796b82593ea --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_rshutdown_001.phpt @@ -0,0 +1,120 @@ +--TEST-- +OPcache explicit cache locks are released when request exits without store +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + usleep(1000); + } +} + +function cache_clear(string $backend): void +{ + if ($backend === 'volatile') { + OPcache\volatile_clear(); + } else { + OPcache\persistent_clear(); + } +} + +function cache_store(string $backend, string $key, mixed $value): void +{ + if ($backend === 'volatile') { + OPcache\volatile_store($key, $value); + } else { + OPcache\persistent_store($key, $value); + } +} + +function cache_fetch(string $backend, string $key, mixed $default = null): mixed +{ + return $backend === 'volatile' + ? OPcache\volatile_fetch($key, $default) + : OPcache\persistent_fetch($key, $default); +} + +function cache_lock(string $backend, string $key): bool +{ + return $backend === 'volatile' + ? OPcache\volatile_lock($key) + : OPcache\persistent_lock($key); +} + +foreach (['volatile', 'persistent'] as $backend) { + $prefix = sys_get_temp_dir() . '/opcache_explicit_exists_lock_rshutdown_' . getmypid() . '_' . $backend; + $readyFile = $prefix . '.ready'; + $resultFile = $prefix . '.result'; + $key = $backend . '_exists_lock_rshutdown_' . getmypid(); + @unlink($readyFile); + @unlink($resultFile); + + echo $backend, "\n"; + cache_clear($backend); + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + $locked = cache_lock($backend, $key); + file_put_contents($resultFile, ($locked ? 'true' : 'false') . "\n"); + file_put_contents($readyFile, 'ready'); + exit(0); + } + + wait_for_file($readyFile); + pcntl_waitpid($pid, $status); + echo file_get_contents($resultFile); + + pcntl_async_signals(true); + pcntl_signal(SIGALRM, static function (): void { + echo "timeout\n"; + exit(1); + }); + pcntl_alarm(3); + var_dump(cache_lock($backend, $key)); + pcntl_alarm(0); + cache_store($backend, $key, 'after'); + var_dump(cache_fetch($backend, $key)); + + @unlink($readyFile); + @unlink($resultFile); +} + +?> +--EXPECT-- +volatile +true +bool(true) +string(5) "after" +persistent +true +bool(true) +string(5) "after" +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_store_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_store_001.phpt new file mode 100644 index 000000000000..7779bc02631a --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_store_001.phpt @@ -0,0 +1,77 @@ +--TEST-- +OPcache explicit cache locks can reserve keys until store +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + +--EXPECT-- +volatile +bool(true) +bool(true) +bool(true) +string(5) "built" +persistent +bool(true) +bool(true) +bool(true) +string(5) "built" diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_stripe_collision_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_stripe_collision_001.phpt new file mode 100644 index 000000000000..f65c6739cab0 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_stripe_collision_001.phpt @@ -0,0 +1,129 @@ +--TEST-- +OPcache explicit cache lock stripes conservatively serialize colliding keys +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + usleep(1000); + } +} + +function run_collision_case(string $backend): void +{ + $prefix = sys_get_temp_dir() . '/opcache_lock_stripe_collision_' . getmypid() . '_' . $backend; + $resultFile = $prefix . '.result'; + $releaseFile = $prefix . '.release'; + @unlink($resultFile); + @unlink($releaseFile); + + cache_clear($backend); + + $baseKey = $backend . '_lock_stripe_base_' . getmypid(); + var_dump(cache_lock($backend, $baseKey)); + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + $blockedKey = null; + for ($i = 0; $i < 4096; $i++) { + $key = $backend . '_lock_stripe_candidate_' . getmypid() . '_' . $i; + if (cache_lock($backend, $key)) { + cache_store($backend, $key, 'release'); + continue; + } + $blockedKey = $key; + break; + } + + if ($blockedKey === null) { + file_put_contents($resultFile, "no-collision\n"); + exit(1); + } + + file_put_contents($resultFile, "blocked\n"); + wait_for_file($releaseFile); + file_put_contents($resultFile, (cache_lock($backend, $blockedKey) ? "released\n" : "still-blocked\n"), FILE_APPEND); + cache_store($backend, $blockedKey, 'released'); + exit(0); + } + + wait_for_file($resultFile); + cache_store($backend, $baseKey, 'parent-release'); + file_put_contents($releaseFile, 'release'); + pcntl_waitpid($pid, $status); + + echo $backend, "\n"; + echo file_get_contents($resultFile); + @unlink($resultFile); + @unlink($releaseFile); +} + +run_collision_case('volatile'); +run_collision_case('persistent'); + +?> +--EXPECT-- +bool(true) +volatile +blocked +released +bool(true) +persistent +blocked +released +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_explicit_cache_prepare_nested_object_reuse_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_prepare_nested_object_reuse_001.phpt new file mode 100644 index 000000000000..0201c531a4e0 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_prepare_nested_object_reuse_001.phpt @@ -0,0 +1,79 @@ +--TEST-- +OPcache explicit cache tracks nested userland object mutations across repeated stores +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + 'first', + 'leaf' => new PreparedNestedLeaf('leaf-first', 1), + 'rows' => [['state' => 'alpha']], +]; + +var_dump(OPcache\volatile_store('nested_volatile_first', $payload)); +OPcache\persistent_store('nested_persistent_first', $payload); + +$payload['name'] = 'second'; +$payload['leaf']->label = 'leaf-second'; +$payload['leaf']->revision = 2; +$payload['rows'][0]['state'] = 'beta'; + +var_dump(OPcache\volatile_store('nested_volatile_second', $payload)); +OPcache\persistent_store('nested_persistent_second', $payload); + +$volatileFirst = OPcache\volatile_fetch('nested_volatile_first'); +$volatileSecond = OPcache\volatile_fetch('nested_volatile_second'); +$persistentFirst = OPcache\persistent_fetch('nested_persistent_first'); +$persistentSecond = OPcache\persistent_fetch('nested_persistent_second'); + +echo $volatileFirst['name'], "\n"; +echo $volatileFirst['leaf']->label, "\n"; +echo $volatileFirst['leaf']->revision, "\n"; +echo $volatileFirst['rows'][0]['state'], "\n"; +echo $volatileSecond['name'], "\n"; +echo $volatileSecond['leaf']->label, "\n"; +echo $volatileSecond['leaf']->revision, "\n"; +echo $volatileSecond['rows'][0]['state'], "\n"; +echo $persistentFirst['name'], "\n"; +echo $persistentFirst['leaf']->label, "\n"; +echo $persistentFirst['leaf']->revision, "\n"; +echo $persistentFirst['rows'][0]['state'], "\n"; +echo $persistentSecond['name'], "\n"; +echo $persistentSecond['leaf']->label, "\n"; +echo $persistentSecond['leaf']->revision, "\n"; +echo $persistentSecond['rows'][0]['state'], "\n"; + +?> +--EXPECT-- +bool(true) +bool(true) +first +leaf-first +1 +alpha +second +leaf-second +2 +beta +first +leaf-first +1 +alpha +second +leaf-second +2 +beta diff --git a/ext/opcache/tests/static_cache_explicit_cache_prepare_safe_direct_mutation_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_prepare_safe_direct_mutation_001.phpt new file mode 100644 index 000000000000..f9c909abee1e --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_prepare_safe_direct_mutation_001.phpt @@ -0,0 +1,52 @@ +--TEST-- +OPcache explicit cache does not reuse prepared shared graphs across safe-direct object mutations +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + 'first', + 'date' => new DateTime('2026-05-01 10:30:45', new DateTimeZone('UTC')), +]; + +var_dump(OPcache\volatile_store('safe_direct_volatile_first', $payload)); +OPcache\persistent_store('safe_direct_persistent_first', $payload); + +$payload['name'] = 'second'; +$payload['date']->modify('+2 days'); + +var_dump(OPcache\volatile_store('safe_direct_volatile_second', $payload)); +OPcache\persistent_store('safe_direct_persistent_second', $payload); + +$volatileFirst = OPcache\volatile_fetch('safe_direct_volatile_first'); +$volatileSecond = OPcache\volatile_fetch('safe_direct_volatile_second'); +$persistentFirst = OPcache\persistent_fetch('safe_direct_persistent_first'); +$persistentSecond = OPcache\persistent_fetch('safe_direct_persistent_second'); + +echo $volatileFirst['name'], "\n"; +echo $volatileFirst['date']->format('Y-m-d H:i:s'), "\n"; +echo $volatileSecond['name'], "\n"; +echo $volatileSecond['date']->format('Y-m-d H:i:s'), "\n"; +echo $persistentFirst['name'], "\n"; +echo $persistentFirst['date']->format('Y-m-d H:i:s'), "\n"; +echo $persistentSecond['name'], "\n"; +echo $persistentSecond['date']->format('Y-m-d H:i:s'), "\n"; + +?> +--EXPECT-- +bool(true) +bool(true) +first +2026-05-01 10:30:45 +second +2026-05-03 10:30:45 +first +2026-05-01 10:30:45 +second +2026-05-03 10:30:45 diff --git a/ext/opcache/tests/static_cache_explicit_cache_prepare_scratch_reuse_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_prepare_scratch_reuse_001.phpt new file mode 100644 index 000000000000..e3c8685497d5 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_prepare_scratch_reuse_001.phpt @@ -0,0 +1,84 @@ +--TEST-- +OPcache explicit cache preserves earlier snapshots across repeated stores from one source value +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + $i, + 'label' => str_repeat($prefix, 24), + 'nested' => ['value' => $i * $multiplier], + ]; + } + + return $rows; +} + +OPcache\volatile_clear(); +OPcache\persistent_clear(); + +$payload = new PreparedScratchPayload('first', build_rows('A', 3)); + +var_dump(OPcache\volatile_store('scratch_volatile_first', $payload)); +OPcache\persistent_store('scratch_persistent_first', $payload); + +$payload->name = 'second'; +$payload->rows[12]['label'] = str_repeat('B', 24); +$payload->rows[12]['nested']['value'] = 777; + +var_dump(OPcache\volatile_store('scratch_volatile_second', $payload)); +OPcache\persistent_store('scratch_persistent_second', $payload); + +$volatileFirst = OPcache\volatile_fetch('scratch_volatile_first'); +$volatileSecond = OPcache\volatile_fetch('scratch_volatile_second'); +$persistentFirst = OPcache\persistent_fetch('scratch_persistent_first'); +$persistentSecond = OPcache\persistent_fetch('scratch_persistent_second'); + +echo $volatileFirst->name, "\n"; +echo $volatileFirst->rows[12]['label'], "\n"; +echo $volatileFirst->rows[12]['nested']['value'], "\n"; +echo $volatileSecond->name, "\n"; +echo $volatileSecond->rows[12]['label'], "\n"; +echo $volatileSecond->rows[12]['nested']['value'], "\n"; +echo $persistentFirst->name, "\n"; +echo $persistentFirst->rows[12]['label'], "\n"; +echo $persistentFirst->rows[12]['nested']['value'], "\n"; +echo $persistentSecond->name, "\n"; +echo $persistentSecond->rows[12]['label'], "\n"; +echo $persistentSecond->rows[12]['nested']['value'], "\n"; + +?> +--EXPECT-- +bool(true) +bool(true) +first +AAAAAAAAAAAAAAAAAAAAAAAA +36 +second +BBBBBBBBBBBBBBBBBBBBBBBB +777 +first +AAAAAAAAAAAAAAAAAAAAAAAA +36 +second +BBBBBBBBBBBBBBBBBBBBBBBB +777 diff --git a/ext/opcache/tests/static_cache_explicit_cache_request_local_object_copy_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_request_local_object_copy_001.phpt new file mode 100644 index 000000000000..038a95600fd8 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_request_local_object_copy_001.phpt @@ -0,0 +1,145 @@ +--TEST-- +OPcache explicit object fetch returns independent request-local object copies +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +label = 'cloned'; + } +} + +final class ExplicitFetchDateTimeCloneHookPayload extends DateTime +{ + public static int $cloneCount = 0; + + public string $label = ''; + + public function __clone() + { + self::$cloneCount++; + $this->label = 'cloned'; + $this->modify('+10 years'); + } +} + +function check_fetch_copies(string $name, callable $store, callable $fetch): void +{ + $store('request_local_copy', new ExplicitFetchCopyPayload($name . '-stored')); + + $first = $fetch('request_local_copy'); + $first->label = $name . '-first-mutated'; + $second = $fetch('request_local_copy'); + var_dump($first === $second); + echo $name, '-second-after-first-mutate=', $second->label, "\n"; + + $second->label = $name . '-mutated'; + echo $name, '-first-after-second-mutate=', $first->label, "\n"; + + $third = $fetch('request_local_copy'); + var_dump($first === $third); + echo $name, '-third=', $third->label, "\n"; + + ExplicitFetchCloneHookPayload::$cloneCount = 0; + $store('request_local_clone_hook', new ExplicitFetchCloneHookPayload($name . '-stored')); + $hookFirst = $fetch('request_local_clone_hook'); + $hookSecond = $fetch('request_local_clone_hook'); + var_dump($hookFirst === $hookSecond); + echo $name, '-clone-hook=', ExplicitFetchCloneHookPayload::$cloneCount, ':', + $hookFirst->label, ':', $hookSecond->label, "\n"; + + ExplicitFetchDateTimeCloneHookPayload::$cloneCount = 0; + $date = new ExplicitFetchDateTimeCloneHookPayload('2026-05-15 12:34:56', new DateTimeZone('Asia/Tokyo')); + $date->label = $name . '-stored'; + $store('request_local_datetime_clone_hook', $date); + $dateFirst = $fetch('request_local_datetime_clone_hook'); + $dateSecond = $fetch('request_local_datetime_clone_hook'); + var_dump($dateFirst === $dateSecond); + echo $name, '-datetime-clone-hook=', ExplicitFetchDateTimeCloneHookPayload::$cloneCount, ':', + $dateFirst->label, ':', $dateSecond->label, "\n"; + echo $name, '-datetime-second-before=', $dateSecond->format(DateTimeInterface::ATOM), "\n"; + + $dateFirst->modify('+1 day'); + $dateFirst->label = $name . '-datetime-first-mutated'; + echo $name, '-datetime-second-after-first-mutate=', $dateSecond->format(DateTimeInterface::ATOM), ':', + $dateSecond->label, "\n"; + + $dateSecond->modify('+2 days'); + $dateSecond->label = $name . '-datetime-second-mutated'; + echo $name, '-datetime-first-after-second-mutate=', $dateFirst->format(DateTimeInterface::ATOM), ':', + $dateFirst->label, "\n"; + + $dateThird = $fetch('request_local_datetime_clone_hook'); + var_dump($dateFirst === $dateThird); + echo $name, '-datetime-third=', $dateThird->format(DateTimeInterface::ATOM), ':', + $dateThird->label, "\n"; +} + +OPcache\volatile_clear(); +OPcache\persistent_clear(); + +check_fetch_copies( + 'volatile', + static fn (string $key, mixed $payload): bool => OPcache\volatile_store($key, $payload), + static fn (string $key): mixed => OPcache\volatile_fetch($key), +); + +check_fetch_copies( + 'persistent', + static function (string $key, mixed $payload): bool { + OPcache\persistent_store($key, $payload); + + return true; + }, + static fn (string $key): mixed => OPcache\persistent_fetch($key), +); + +?> +--EXPECT-- +bool(false) +volatile-second-after-first-mutate=volatile-stored +volatile-first-after-second-mutate=volatile-first-mutated +bool(false) +volatile-third=volatile-stored +bool(false) +volatile-clone-hook=0:volatile-stored:volatile-stored +bool(false) +volatile-datetime-clone-hook=0:volatile-stored:volatile-stored +volatile-datetime-second-before=2026-05-15T12:34:56+09:00 +volatile-datetime-second-after-first-mutate=2026-05-15T12:34:56+09:00:volatile-stored +volatile-datetime-first-after-second-mutate=2026-05-16T12:34:56+09:00:volatile-datetime-first-mutated +bool(false) +volatile-datetime-third=2026-05-15T12:34:56+09:00:volatile-stored +bool(false) +persistent-second-after-first-mutate=persistent-stored +persistent-first-after-second-mutate=persistent-first-mutated +bool(false) +persistent-third=persistent-stored +bool(false) +persistent-clone-hook=0:persistent-stored:persistent-stored +bool(false) +persistent-datetime-clone-hook=0:persistent-stored:persistent-stored +persistent-datetime-second-before=2026-05-15T12:34:56+09:00 +persistent-datetime-second-after-first-mutate=2026-05-15T12:34:56+09:00:persistent-stored +persistent-datetime-first-after-second-mutate=2026-05-16T12:34:56+09:00:persistent-datetime-first-mutated +bool(false) +persistent-datetime-third=2026-05-15T12:34:56+09:00:persistent-stored diff --git a/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt new file mode 100644 index 000000000000..e580dfe9fc32 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt @@ -0,0 +1,140 @@ +--TEST-- +OPcache explicit fetch request-local slots clone __DirectCacheSafe objects +--EXTENSIONS-- +opcache +spl +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +peer = $peer; + + store_value($backend, $backend . '-date', [ + 'date' => $date, + 'again' => $date, + 'peer' => $peer, + ]); + + $first = fetch_value($backend, $backend . '-date'); + $warm = fetch_value($backend, $backend . '-date'); + echo $backend, '-date-same-object=', $warm['date'] === $warm['again'] ? 'yes' : 'no', "\n"; + echo $backend, '-date-shared-peer=', $warm['date']->peer === $warm['peer'] ? 'yes' : 'no', "\n"; + echo $backend, '-date-clone-calls=', SafeDirectSlotDateTime::$cloneCalls, "\n"; + + $first['date']->modify('+1 day'); + $first['date']->peer->label = $backend . '-changed'; + $second = fetch_value($backend, $backend . '-date'); + echo $backend, '-date-second=', $second['date']->format('Y-m-d'), ':', $second['date']->peer->label, "\n"; + echo $backend, '-date-second-is-first=', $second['date'] === $first['date'] ? 'yes' : 'no', "\n"; + echo $backend, '-date-clone-calls=', SafeDirectSlotDateTime::$cloneCalls, "\n"; + + $collectionPeer = new SafeDirectSlotPeer($backend . '-collection-stored'); + $collection = new SafeDirectSlotArrayObject(['peer' => $collectionPeer]); + + store_value($backend, $backend . '-collection', [ + 'collection' => $collection, + 'again' => $collection, + 'peer' => $collectionPeer, + ]); + + $collectionFirst = fetch_value($backend, $backend . '-collection'); + $collectionWarm = fetch_value($backend, $backend . '-collection'); + echo $backend, '-collection-same-object=', $collectionWarm['collection'] === $collectionWarm['again'] ? 'yes' : 'no', "\n"; + echo $backend, '-collection-shared-peer=', $collectionWarm['collection']['peer'] === $collectionWarm['peer'] ? 'yes' : 'no', "\n"; + echo $backend, '-collection-clone-calls=', SafeDirectSlotArrayObject::$cloneCalls, "\n"; + + $collectionFirst['collection']['peer']->label = $backend . '-collection-changed'; + $collectionFirst['collection']['local'] = 'local-only'; + $collectionSecond = fetch_value($backend, $backend . '-collection'); + echo $backend, '-collection-second=', $collectionSecond['collection']['peer']->label, ':', count($collectionSecond['collection']), "\n"; + echo $backend, '-collection-second-is-first=', $collectionSecond['collection'] === $collectionFirst['collection'] ? 'yes' : 'no', "\n"; + echo $backend, '-collection-clone-calls=', SafeDirectSlotArrayObject::$cloneCalls, "\n"; +} + +OPcache\volatile_clear(); +OPcache\persistent_clear(); + +run_safe_direct_slot_scenario('volatile'); +run_safe_direct_slot_scenario('persistent'); + +?> +--EXPECT-- +bool(true) +volatile-date-same-object=no +volatile-date-shared-peer=no +volatile-date-clone-calls=0 +volatile-date-second=2026-01-01:volatile-stored +volatile-date-second-is-first=no +volatile-date-clone-calls=0 +bool(true) +volatile-collection-same-object=no +volatile-collection-shared-peer=no +volatile-collection-clone-calls=0 +volatile-collection-second=volatile-collection-stored:1 +volatile-collection-second-is-first=no +volatile-collection-clone-calls=0 +persistent-stored +persistent-date-same-object=no +persistent-date-shared-peer=no +persistent-date-clone-calls=0 +persistent-date-second=2026-01-01:persistent-stored +persistent-date-second-is-first=no +persistent-date-clone-calls=0 +persistent-stored +persistent-collection-same-object=no +persistent-collection-shared-peer=no +persistent-collection-clone-calls=0 +persistent-collection-second=persistent-collection-stored:1 +persistent-collection-second-is-first=no +persistent-collection-clone-calls=0 diff --git a/ext/opcache/tests/static_cache_explicit_cache_request_local_slot_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_request_local_slot_001.phpt new file mode 100644 index 000000000000..e7d188a51eeb --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_request_local_slot_001.phpt @@ -0,0 +1,130 @@ +--TEST-- +OPcache explicit fetch clones object-bearing request-local results +--EXTENSIONS-- +opcache +spl +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +label, "\n"; +} + +OPcache\volatile_clear(); +OPcache\persistent_clear(); + +$volatilePayload = ['object' => new ExplicitRequestLocalSlotPayload('volatile-stored')]; +var_dump(OPcache\volatile_store('request_local_slot', $volatilePayload)); + +$volatileFirst = OPcache\volatile_fetch('request_local_slot'); +$volatileWarm = OPcache\volatile_fetch('request_local_slot'); +var_dump($volatileFirst['object'] === $volatileWarm['object']); +$volatileFirst['object']->label = 'volatile-mutated-in-request'; +$volatileSecond = OPcache\volatile_fetch('request_local_slot'); + +var_dump($volatileFirst['object'] === $volatileSecond['object']); +dump_label('volatile-second', $volatileSecond['object']); + +var_dump(OPcache\volatile_store('request_local_slot', ['object' => new ExplicitRequestLocalSlotPayload('volatile-replaced')])); +$volatileReplaced = OPcache\volatile_fetch('request_local_slot'); +dump_label('volatile-replaced', $volatileReplaced['object']); + +OPcache\volatile_delete('request_local_slot'); +var_dump(OPcache\volatile_fetch('request_local_slot', 'volatile-missing')); + +var_dump(OPcache\volatile_store('request_local_ttl', ['object' => new ExplicitRequestLocalSlotPayload('volatile-ttl')], 1)); +$volatileTtl = OPcache\volatile_fetch('request_local_ttl'); +dump_label('volatile-ttl-first', $volatileTtl['object']); +sleep(2); +var_dump(OPcache\volatile_fetch('request_local_ttl', 'volatile-expired')); + +var_dump(OPcache\volatile_store('request_local_datetime', new DateTime('2026-01-01 00:00:00', new DateTimeZone('UTC')))); +$volatileDateFirst = OPcache\volatile_fetch('request_local_datetime'); +$volatileDateWarm = OPcache\volatile_fetch('request_local_datetime'); +var_dump($volatileDateFirst === $volatileDateWarm); +$volatileDateFirst->modify('+1 day'); +$volatileDateSecond = OPcache\volatile_fetch('request_local_datetime'); +var_dump($volatileDateFirst === $volatileDateSecond); +echo $volatileDateSecond->format('Y-m-d'), "\n"; + +var_dump(OPcache\volatile_store('request_local_array_object', new ArrayObject(['label' => 'stored']))); +$volatileArrayObjectFirst = OPcache\volatile_fetch('request_local_array_object'); +$volatileArrayObjectWarm = OPcache\volatile_fetch('request_local_array_object'); +var_dump($volatileArrayObjectFirst === $volatileArrayObjectWarm); +$volatileArrayObjectFirst->exchangeArray(['label' => 'changed']); +$volatileArrayObjectSecond = OPcache\volatile_fetch('request_local_array_object'); +var_dump($volatileArrayObjectFirst === $volatileArrayObjectSecond); +echo $volatileArrayObjectSecond['label'], "\n"; + +$volatileList = new ExplicitRequestLocalSlotUnsupportedList(); +$volatileList->push('stored'); +var_dump(OPcache\volatile_store('request_local_unsupported_internal', $volatileList)); +$volatileListFirst = OPcache\volatile_fetch('request_local_unsupported_internal'); +$volatileListWarm = OPcache\volatile_fetch('request_local_unsupported_internal'); +var_dump($volatileListFirst === $volatileListWarm); +$volatileListFirst->push('changed'); +$volatileListSecond = OPcache\volatile_fetch('request_local_unsupported_internal'); +var_dump($volatileListFirst === $volatileListSecond); +echo $volatileListSecond->count(), ':', $volatileListSecond->bottom(), "\n"; + +OPcache\persistent_store('request_local_slot', ['object' => new ExplicitRequestLocalSlotPayload('persistent-stored')]); +$persistentFirst = OPcache\persistent_fetch('request_local_slot'); +$persistentWarm = OPcache\persistent_fetch('request_local_slot'); +var_dump($persistentFirst['object'] === $persistentWarm['object']); +$persistentFirst['object']->label = 'persistent-mutated-in-request'; +$persistentSecond = OPcache\persistent_fetch('request_local_slot'); + +var_dump($persistentFirst['object'] === $persistentSecond['object']); +dump_label('persistent-second', $persistentSecond['object']); + +OPcache\persistent_store('request_local_slot', ['object' => new ExplicitRequestLocalSlotPayload('persistent-replaced')]); +$persistentReplaced = OPcache\persistent_fetch('request_local_slot'); +dump_label('persistent-replaced', $persistentReplaced['object']); + +OPcache\persistent_delete('request_local_slot'); +var_dump(OPcache\persistent_fetch('request_local_slot', 'persistent-missing')); + +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) +volatile-second=volatile-stored +bool(true) +volatile-replaced=volatile-replaced +string(16) "volatile-missing" +bool(true) +volatile-ttl-first=volatile-ttl +string(16) "volatile-expired" +bool(true) +bool(false) +bool(false) +2026-01-01 +bool(true) +bool(false) +bool(false) +stored +bool(true) +bool(false) +bool(false) +1:stored +bool(false) +bool(false) +persistent-second=persistent-stored +persistent-replaced=persistent-replaced +string(18) "persistent-missing" diff --git a/ext/opcache/tests/static_cache_explicit_cache_shared_graph_array_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_shared_graph_array_001.phpt new file mode 100644 index 000000000000..f9ccc3867918 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_shared_graph_array_001.phpt @@ -0,0 +1,66 @@ +--TEST-- +OPcache explicit cache stores nested array shared graphs without relocation-sensitive layout +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + [ + ['path' => '/users', 'methods' => ['GET', 'POST']], + ['path' => '/posts', 'defaults' => ['page' => 1, 'sort' => 'desc']], + ], + 'nodes' => [ + new RelocatableArrayNode(['flags' => ['auth' => true, 'cache' => false]]), + new RelocatableArrayNode(['flags' => ['auth' => false, 'cache' => true]]), + ], +]; + +var_dump(OPcache\volatile_store('relocatable-array-payload', $payload)); +$copy = OPcache\volatile_fetch('relocatable-array-payload'); + +var_dump($copy['routes'][0]['methods'][1]); +var_dump($copy['routes'][1]['defaults']['page']); +var_dump($copy['nodes'][0] instanceof RelocatableArrayNode); +var_dump($copy['nodes'][0]->metadata['flags']['auth']); +var_dump($copy['nodes'][1]->metadata['flags']['cache']); + +OPcache\persistent_store('relocatable-array-object', new RelocatableArrayNode([ + 'matrix' => [[1, 2], [3, 4]], +])); +$global = OPcache\persistent_fetch('relocatable-array-object'); + +var_dump($global instanceof RelocatableArrayNode); +var_dump($global->metadata['matrix'][1][0]); + +var_dump(OPcache\volatile_store('relocatable-array-payload', [ + 'routes' => [['path' => '/health', 'methods' => ['GET']]], +])); +$updated = OPcache\volatile_fetch('relocatable-array-payload'); + +var_dump($updated['routes'][0]['path']); +var_dump($updated['routes'][0]['methods'][0]); + +?> +--EXPECT-- +bool(true) +string(4) "POST" +int(1) +bool(true) +bool(true) +bool(true) +bool(true) +int(3) +bool(true) +string(7) "/health" +string(3) "GET" diff --git a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt new file mode 100644 index 000000000000..da6e31c55e0d --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt @@ -0,0 +1,68 @@ +--TEST-- +OPcache explicit cache signatures expose storable values and array fallbacks +--EXTENSIONS-- +opcache +--FILE-- +getName(); + + return $type->allowsNull() && $name !== 'null' && $name !== 'mixed' ? '?' . $name : $name; + } + + $order = [ + 'null' => 0, + 'bool' => 1, + 'int' => 2, + 'float' => 3, + 'string' => 4, + 'array' => 5, + 'object' => 6, + ]; + $names = array_map( + static fn (ReflectionNamedType $named): string => $named->getName(), + $type->getTypes(), + ); + usort($names, static fn (string $left, string $right): int => ($order[$left] ?? 99) <=> ($order[$right] ?? 99)); + + return implode('|', $names); +} + +foreach ([ + 'OPcache\\volatile_store' => ['value'], + 'OPcache\\volatile_fetch' => ['default'], + 'OPcache\\volatile_fetch_array' => ['default'], + 'OPcache\\persistent_store' => ['value'], + 'OPcache\\persistent_fetch' => ['default'], + 'OPcache\\persistent_fetch_array' => ['default'], +] as $function => $parameters) { + $reflection = new ReflectionFunction($function); + $parts = [$function]; + $parameterMap = []; + foreach ($reflection->getParameters() as $parameter) { + $parameterMap[$parameter->getName()] = $parameter; + } + foreach ($parameters as $parameterName) { + $parameter = $parameterMap[$parameterName]; + $parts[] = '$' . $parameterName . '=' . describe_type($parameter->getType()); + } + $parts[] = 'params=' . $reflection->getNumberOfRequiredParameters() . '/' . $reflection->getNumberOfParameters(); + $parts[] = 'return=' . describe_type($reflection->getReturnType()); + echo implode(' ', $parts), "\n"; +} + +?> +--EXPECT-- +OPcache\volatile_store $value=null|bool|int|float|string|array|object params=2/3 return=bool +OPcache\volatile_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object +OPcache\volatile_fetch_array $default=?array params=1/2 return=?array +OPcache\persistent_store $value=null|bool|int|float|string|array|object params=2/2 return=void +OPcache\persistent_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object +OPcache\persistent_fetch_array $default=?array params=1/2 return=?array diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt new file mode 100644 index 000000000000..7e34a185aa43 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt @@ -0,0 +1,39 @@ +--TEST-- +OPcache explicit store_array APIs reject non-string keys before storing any entries +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + ['OPcache\\volatile_store_array', 'OPcache\\volatile_fetch'], + 'persistent' => ['OPcache\\persistent_store_array', 'OPcache\\persistent_fetch'], +] as $label => [$storeArray, $fetch]) { + $key = $label . '_first'; + + try { + $storeArray([ + $key => 'stored', + 0 => 'bad', + ]); + } catch (ValueError $exception) { + echo $label, ': ', $exception->getMessage(), "\n"; + } + + var_dump($fetch($key, 'missing')); +} + +?> +--EXPECT-- +volatile: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys +string(7) "missing" +persistent: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys +string(7) "missing" diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_delete_fork_reuse_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_delete_fork_reuse_001.phpt new file mode 100644 index 000000000000..324ceebb59a9 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_store_delete_fork_reuse_001.phpt @@ -0,0 +1,109 @@ +--TEST-- +OPcache explicit volatile and persistent delete frees payload memory across processes +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +opcache.static_cache.persistent_size_mb=8 +--FILE-- + +--EXPECT-- +-- volatile -- +bool(true) +bool(true) +bool(true) +string(7) "missing" +bool(true) +int(1800000) +int(1800000) +int(1500000) +-- persistent -- +bool(true) +bool(true) +bool(true) +string(7) "missing" +bool(true) +int(1800000) +int(1800000) +int(1500000) diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_delete_request_reuse_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_delete_request_reuse_001.phpt new file mode 100644 index 000000000000..916d1621780b --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_store_delete_request_reuse_001.phpt @@ -0,0 +1,122 @@ +--TEST-- +OPcache explicit volatile and persistent delete frees payload memory across requests +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +-- volatile -- +bool(true) +bool(true) +bool(true) +string(7) "missing" +bool(true) +int(1800000) +int(1800000) +int(1500000) +-- persistent -- +bool(true) +bool(true) +bool(true) +string(7) "missing" +bool(true) +int(1800000) +int(1800000) +int(1500000) diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_delete_zts_threads_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_delete_zts_threads_001.phpt new file mode 100644 index 000000000000..ab3fa6fd11c0 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_store_delete_zts_threads_001.phpt @@ -0,0 +1,246 @@ +--TEST-- +OPcache explicit volatile and persistent delete frees payload memory across threads +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- + MAX_COMMAND_OUTPUT_BYTES) { + return substr($contents, 0, MAX_COMMAND_OUTPUT_BYTES) . "\n...[output truncated]\n"; + } + + return $contents; +} + +function runCommand(array $command): array +{ + $stdoutFile = tempnam(sys_get_temp_dir(), 'opcache-explicit-zts-out-'); + $stderrFile = tempnam(sys_get_temp_dir(), 'opcache-explicit-zts-err-'); + if ($stdoutFile === false || $stderrFile === false) { + throw new RuntimeException('failed to create temporary files'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['file', $stdoutFile, 'w'], + 2 => ['file', $stderrFile, 'w'], + ]; + + $process = proc_open($command, $descriptorSpec, $pipes); + if (!is_resource($process)) { + @unlink($stdoutFile); + @unlink($stderrFile); + throw new RuntimeException('proc_open failed'); + } + + fclose($pipes[0]); + $status = proc_close($process); + + $stdout = readCommandOutput($stdoutFile); + $stderr = readCommandOutput($stderrFile); + @unlink($stdoutFile); + @unlink($stderrFile); + + return [$status, $stdout, $stderr]; +} + +function parseBuildFlags(string $flags): array +{ + $tokens = preg_split('/\s+/', trim($flags)); + if ($tokens === false) { + return []; + } + + return array_values(array_filter($tokens, static function (string $token): bool { + return $token !== '' && !str_contains($token, '$('); + })); +} + +function resolveBuildFlags(string $buildRoot, array $variables): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + $flags = []; + + if ($makefile === false) { + return $flags; + } + + foreach ($variables as $variable) { + if (preg_match('/^' . preg_quote($variable, '/') . '\s*=\s*(.+)$/m', $makefile, $matches)) { + $flags = array_merge($flags, parseBuildFlags($matches[1])); + } + } + + return $flags; +} + +function resolveBuildCommand(string $buildRoot, string $variable, array $fallback): array +{ + $value = getenv($variable); + if (is_string($value) && $value !== '') { + $tokens = parseBuildFlags($value); + if ($tokens !== []) { + return $tokens; + } + } + + $tokens = resolveBuildFlags($buildRoot, [$variable]); + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveExtraLibs(string $buildRoot): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + if ($makefile !== false && preg_match('/^EXTRA_LIBS\s*=\s*(.+)$/m', $makefile, $matches)) { + $libs = preg_split('/\s+/', trim($matches[1])); + if ($libs !== false && $libs !== ['']) { + return $libs; + } + } + + return [ + '-lm', + '-lpthread', + '-ldl', + '-lresolv', + '-lutil', + ]; +} + +function resolveBuildRoot(string $root): string +{ + $buildRoot = null; + $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; + if ($php) { + $php = realpath($php); + if ($php !== false) { + $candidate = dirname($php, 3); + if (file_exists($candidate . '/.libs/libphp.a') || file_exists($candidate . '/libs/libphp.a')) { + $buildRoot = $candidate; + } + } + } + + if ($buildRoot === null && (file_exists($root . '/.libs/libphp.a') || file_exists($root . '/libs/libphp.a'))) { + $buildRoot = $root; + } + + if ($buildRoot === null) { + throw new RuntimeException('Failed to resolve build root'); + } + + return $buildRoot; +} + +$root = realpath(__DIR__ . '/../../..'); +if ($root === false) { + throw new RuntimeException('Failed to resolve source root'); +} + +$buildRoot = resolveBuildRoot($root); +$source = __DIR__ . '/helpers/explicit_cache_store_delete_zts_threads_001.c'; +$binary = __DIR__ . '/helpers/explicit_cache_store_delete_zts_threads_001.out'; +$compiler = resolveBuildCommand($buildRoot, 'CC', ['gcc']); +$buildFlags = resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$extraLibs = resolveExtraLibs($buildRoot); + +$compileCommand = [ + ...$compiler, + ...$buildFlags, + '-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '-DHAVE_CONFIG_H', + '-I' . $buildRoot, + '-I' . $buildRoot . '/main', + '-I' . $buildRoot . '/Zend', + '-I' . $buildRoot . '/TSRM', + '-I' . $buildRoot . '/sapi/embed', + '-I' . $root, + '-I' . $root . '/main', + '-I' . $root . '/Zend', + '-I' . $root . '/TSRM', + '-I' . $root . '/sapi/embed', + '-o', + $binary, + $source, + $root . '/sapi/embed/php_embed.c', + file_exists($buildRoot . '/.libs/libphp.a') ? $buildRoot . '/.libs/libphp.a' : $buildRoot . '/libs/libphp.a', +]; +$compileCommand = array_merge($compileCommand, $extraLibs); + +[$status, $stdout, $stderr] = runCommand($compileCommand); +if ($status !== 0) { + echo "compile failed: {$status}\n"; + echo $stdout; + echo $stderr; + return; +} + +[$status, $stdout, $stderr] = runCommand([$binary]); +echo $stdout; +if ($status !== 0) { + echo "run failed: {$status}\n"; + echo $stderr; +} + +?> +--CLEAN-- + +--EXPECT-- +ok diff --git a/ext/opcache/tests/static_cache_persistent_cache_api_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_api_001.phpt new file mode 100644 index 000000000000..536430a39c7b --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_api_001.phpt @@ -0,0 +1,55 @@ +--TEST-- +OPcache persistent_store, persistent_fetch, and persistent_exists public API +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + 1]); + var_dump(OPcache\persistent_exists('shared')); + return; +} + +var_dump(OPcache\persistent_exists('shared')); +var_dump(OPcache\persistent_fetch('shared', 'fallback')); +var_dump(OPcache\persistent_fetch('missing')); +var_dump(OPcache\persistent_fetch('missing', 'fallback')); + +OPcache\persistent_store('null', null); +var_dump(OPcache\persistent_fetch('null', 'fallback')); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); + +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_cache_api_001.php?action=write'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_cache_api_001.php?action=read'); + +?> +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +array(1) { + ["v"]=> + int(1) +} +NULL +string(8) "fallback" +NULL diff --git a/ext/opcache/tests/static_cache_persistent_cache_array_mutation_overflow_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_array_mutation_overflow_001.phpt new file mode 100644 index 000000000000..793209aed14c --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_array_mutation_overflow_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +OPcache PersistentStatic throws an exception when array mutation exhausts persistent cache shared memory +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=8 +opcache.optimization_level=0 +opcache.file_update_protection=0 +opcache.jit=0 +--FILE-- +getMessage(), "\n"; + OPcache\persistent_clear(); +} + +?> +--EXPECT-- +OPcache\StaticCacheException: not enough shared memory left diff --git a/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt new file mode 100644 index 000000000000..d2d085885e14 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt @@ -0,0 +1,49 @@ +--TEST-- +OPcache persistent atomic increment creates missing keys without TTL +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +getMessage(), "\n"; +} + +try { + OPcache\persistent_atomic_increment('extra', 1, 1); +} catch (ArgumentCountError $exception) { + echo "too-many-args\n"; +} + +?> +--EXPECT-- +int(7) +int(7) +int(10) +int(10) +bool(true) +int(11) +int(11) +bool(true) +Cache key "missing_down" was not found +too-many-args diff --git a/ext/opcache/tests/static_cache_persistent_cache_atomic_non_integer_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_atomic_non_integer_001.phpt new file mode 100644 index 000000000000..4251faa18f31 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_atomic_non_integer_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +OPcache persistent atomic operations reject non-integer entries +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +getMessage(), "\n"; +} + +try { + OPcache\persistent_atomic_decrement('text'); +} catch (StaticCacheException $exception) { + echo $exception->getMessage(), "\n"; +} + +?> +--EXPECT-- +Atomic increment requires an integer value +Atomic decrement requires an integer value diff --git a/ext/opcache/tests/static_cache_persistent_cache_batch_atomic_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_batch_atomic_001.phpt new file mode 100644 index 000000000000..04fcfe2124c7 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_batch_atomic_001.phpt @@ -0,0 +1,62 @@ +--TEST-- +OPcache persistent cache batch and atomic public API +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + 10, + 'name' => 'php', + 'null' => null, +]); + +$fallback = ['fallback']; + +var_dump(OPcache\persistent_fetch_array(['count', 'name', 'missing'], $fallback)); +var_dump(OPcache\persistent_atomic_increment('count')); +var_dump(OPcache\persistent_atomic_decrement('count', 3)); + +OPcache\persistent_delete_array(['name', 'missing']); +var_dump(OPcache\persistent_fetch_array(['count', 'name', 'null'], $fallback)); + +OPcache\persistent_clear(); +var_dump(OPcache\persistent_fetch_array(['count'], $fallback)); + +?> +--EXPECT-- +array(3) { + ["count"]=> + int(10) + ["name"]=> + string(3) "php" + ["missing"]=> + array(1) { + [0]=> + string(8) "fallback" + } +} +int(11) +int(8) +array(3) { + ["count"]=> + int(8) + ["name"]=> + array(1) { + [0]=> + string(8) "fallback" + } + ["null"]=> + NULL +} +array(1) { + ["count"]=> + array(1) { + [0]=> + string(8) "fallback" + } +} diff --git a/ext/opcache/tests/static_cache_persistent_cache_clear_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_clear_001.phpt new file mode 100644 index 000000000000..8838a2a07297 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_clear_001.phpt @@ -0,0 +1,58 @@ +--TEST-- +OPcache persistent_clear clears only the persistent cache API namespace +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +string(14) "volatile-value" +string(18) "missing-persistent" +string(14) "volatile-value" +string(18) "missing-persistent" diff --git a/ext/opcache/tests/static_cache_persistent_cache_clear_combined_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_clear_combined_001.phpt new file mode 100644 index 000000000000..c8c9d1e16dd1 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_clear_combined_001.phpt @@ -0,0 +1,110 @@ +--TEST-- +OPcache persistent_clear drops combined persistent entries without corrupting refill or volatile entries +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + $i, + 'text' => str_repeat($prefix, 32), + 'nested' => ['value' => $i * $multiplier], + ]; + } + + return $rows; +} + +function build_graph(string $prefix, int $multiplier): PersistentClearCombinedNode +{ + return new PersistentClearCombinedNode(build_rows($prefix, $multiplier)); +} + +$action = $_GET['action'] ?? 'seed'; + +if ($action === 'seed') { + OPcache\volatile_clear(); + OPcache\persistent_clear(); + var_dump(OPcache\volatile_store('local-string', str_repeat('L', 400000))); + OPcache\persistent_store('persistent-string', str_repeat('G', 400000)); + OPcache\persistent_store('persistent-graph', build_graph('O', 3)); + $graph = OPcache\persistent_fetch('persistent-graph'); + var_dump(strlen(OPcache\persistent_fetch('persistent-string'))); + var_dump($graph->rows[123]['text']); + return; +} + +if ($action === 'clear') { + OPcache\persistent_clear(); + var_dump(strlen(OPcache\volatile_fetch('local-string', 'missing-volatile'))); + var_dump(OPcache\persistent_fetch('persistent-string', 'missing-persistent')); + var_dump(OPcache\persistent_fetch('persistent-graph', 'missing-graph')); + return; +} + +if ($action === 'refill') { + OPcache\persistent_store('persistent-string', str_repeat('H', 400000)); + OPcache\persistent_store('persistent-graph', build_graph('N', 7)); + $graph = OPcache\persistent_fetch('persistent-graph'); + var_dump(strlen(OPcache\persistent_fetch('persistent-string'))); + var_dump($graph->rows[123]['text']); + var_dump($graph->rows[123]['nested']['value']); + return; +} + +$graph = OPcache\persistent_fetch('persistent-graph'); +var_dump(strlen(OPcache\volatile_fetch('local-string', 'missing-volatile'))); +var_dump(strlen(OPcache\persistent_fetch('persistent-string'))); +var_dump($graph->rows[123]['text']); +var_dump($graph->rows[123]['nested']['value']); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=8 -d opcache.static_cache.persistent_size_mb=8 -d opcache.file_update_protection=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_cache_clear_combined_001.php'; +echo file_get_contents($base . '?action=seed'); +echo file_get_contents($base . '?action=clear'); +echo file_get_contents($base . '?action=refill'); +echo file_get_contents($base . '?action=read'); + +?> +--CLEAN-- + +--EXPECT-- +bool(true) +int(400000) +string(32) "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" +int(400000) +string(18) "missing-persistent" +string(13) "missing-graph" +int(400000) +string(32) "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN" +int(861) +int(400000) +int(400000) +string(32) "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN" +int(861) diff --git a/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt new file mode 100644 index 000000000000..047fc02558c7 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +OPcache volatile_cache_info and persistent_cache_info report separate cache backends +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1 +1,1,volatile-value,2 +bool(true) +bool(true) +int(33554432) +int(33554432) diff --git a/ext/opcache/tests/static_cache_persistent_cache_lock_atomic_increment_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_lock_atomic_increment_001.phpt new file mode 100644 index 000000000000..0f557af2ee34 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_lock_atomic_increment_001.phpt @@ -0,0 +1,79 @@ +--TEST-- +OPcache persistent_lock is released by persistent_atomic_increment +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=32 +--FILE-- += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + usleep(1000); + } +} + +$prefix = sys_get_temp_dir() . '/opcache_persistent_exists_lock_' . getmypid(); +$readyFile = $prefix . '.ready'; +$resultFile = $prefix . '.result'; +@unlink($readyFile); +@unlink($resultFile); + +$key = 'persistent_exists_lock_' . getmypid(); +OPcache\persistent_clear(); + +var_dump(OPcache\persistent_lock($key)); +var_dump(OPcache\persistent_atomic_increment($key, 9)); + +$pid = pcntl_fork(); +if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); +} + +if ($pid === 0) { + pcntl_async_signals(true); + pcntl_signal(SIGALRM, static function () use ($resultFile): void { + file_put_contents($resultFile, "timeout\n"); + exit(1); + }); + pcntl_alarm(3); + file_put_contents($readyFile, 'ready'); + $exists = OPcache\persistent_lock($key); + $value = OPcache\persistent_fetch($key); + pcntl_alarm(0); + file_put_contents($resultFile, ($exists ? 'true' : 'false') . "\n" . $value . "\n"); + exit(0); +} + +wait_for_file($readyFile); +pcntl_waitpid($pid, $status); +echo file_get_contents($resultFile); +@unlink($readyFile); +@unlink($resultFile); + +?> +--EXPECT-- +bool(true) +int(9) +true +9 +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_persistent_cache_overflow_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_overflow_001.phpt new file mode 100644 index 000000000000..37cb7bcab62e --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_overflow_001.phpt @@ -0,0 +1,30 @@ +--TEST-- +OPcache PersistentStatic throws an exception when property assignment exhausts persistent cache shared memory +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=8 +opcache.optimization_level=0 +opcache.file_update_protection=0 +opcache.jit=0 +--FILE-- +getMessage(), "\n"; + OPcache\persistent_clear(); +} + +?> +--EXPECT-- +OPcache\StaticCacheException: not enough shared memory left diff --git a/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt new file mode 100644 index 000000000000..e58ca6a82307 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt @@ -0,0 +1,29 @@ +--TEST-- +OPcache persistent_store throws StaticCacheException when persistent cache memory is exhausted +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=8 +--FILE-- +getMessage(), "\n"; +} + +var_dump(OPcache\persistent_fetch('small', 'fallback')); +var_dump(OPcache\persistent_fetch('missing', 'fallback')); + +?> +--EXPECT-- +OPcache\StaticCacheException: not enough shared memory left +string(2) "ok" +string(8) "fallback" diff --git a/ext/opcache/tests/static_cache_persistent_static_array_mutation_fast_path_001.phpt b/ext/opcache/tests/static_cache_persistent_static_array_mutation_fast_path_001.phpt new file mode 100644 index 000000000000..ca6ad6ff1976 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_array_mutation_fast_path_001.phpt @@ -0,0 +1,111 @@ +--TEST-- +OPcache PersistentStatic publishes class, property, and method array mutations immediately +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +reset +seed-class=foo,bar +seed-property=foo,bar +seed-method=foo,bar +read-class=foo,bar +read-property=foo,bar +read-method=foo,bar +mutate-class=foo,bar,baz +mutate-property=foo,bar,baz +mutate-method=foo,bar,baz +read-class=foo,bar,baz +read-property=foo,bar,baz +read-method=foo,bar,baz diff --git a/ext/opcache/tests/static_cache_persistent_static_array_mutation_jit_001.phpt b/ext/opcache/tests/static_cache_persistent_static_array_mutation_jit_001.phpt new file mode 100644 index 000000000000..de81f6623d37 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_array_mutation_jit_001.phpt @@ -0,0 +1,121 @@ +--TEST-- +OPcache PersistentStatic array mutations remain visible through tracing JIT static slot reads +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECT-- +reset +seed-before=1,1000,1000 +seed-after=1,2000,2000 +read=1,2000,2000 diff --git a/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt b/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt new file mode 100644 index 000000000000..faf2af133f21 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt @@ -0,0 +1,104 @@ +--TEST-- +OPcache PersistentStatic keeps class, property, and method attribute scopes separate +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +reset +1,1,1,1,1,1 +2,2,2,1,2,1 +2,3,2,3,0,0,1,3 diff --git a/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt b/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt new file mode 100644 index 000000000000..d7828b8dbfdf --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt @@ -0,0 +1,122 @@ +--TEST-- +OPcache static attribute decode capture is cleared after cache miss +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +bag[] = 'seed'; + var_dump(OPcache\volatile_store('capture_miss_explicit', $probe)); + echo 'volatile_entries=', OPcache\volatile_cache_info()['entry_count'], "\n"; + return; +} + +if ($action === 'miss_then_explicit_mutate') { + match ($backend) { + 'cached' => CaptureMissCachedMethod::touch(), + 'persistent' => CaptureMissPersistentMethod::touch(), + default => throw new RuntimeException('unknown backend'), + }; + + $explicit = OPcache\volatile_fetch('capture_miss_explicit'); + $explicit->bag[] = 'mutated outside static attribute'; + + echo 'explicit_bag=', count($explicit->bag), "\n"; + echo 'volatile_entries=', OPcache\volatile_cache_info()['entry_count'], "\n"; + echo 'persistent_entries=', OPcache\persistent_cache_info()['entry_count'], "\n"; + return; +} + +if ($action === 'read_static') { + match ($backend) { + 'cached' => CaptureMissCachedMethod::touch(), + 'persistent' => CaptureMissPersistentMethod::touch(), + default => throw new RuntimeException('unknown backend'), + }; + echo 'volatile_entries=', OPcache\volatile_cache_info()['entry_count'], "\n"; + echo 'persistent_entries=', OPcache\persistent_cache_info()['entry_count'], "\n"; + return; +} + +throw new RuntimeException('unknown action'); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_capture_miss_001.php'; +foreach (['cached', 'persistent'] as $backend) { + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=seed'); + echo file_get_contents($base . '?action=miss_then_explicit_mutate&backend=' . $backend); + echo file_get_contents($base . '?action=read_static&backend=' . $backend); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +bool(true) +volatile_entries=1 +explicit_bag=2 +volatile_entries=1 +persistent_entries=0 +volatile_entries=1 +persistent_entries=0 +reset +bool(true) +volatile_entries=1 +explicit_bag=2 +volatile_entries=1 +persistent_entries=0 +volatile_entries=1 +persistent_entries=0 diff --git a/ext/opcache/tests/static_cache_persistent_static_capture_tracking_001.phpt b/ext/opcache/tests/static_cache_persistent_static_capture_tracking_001.phpt new file mode 100644 index 000000000000..32c1010f9870 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_capture_tracking_001.phpt @@ -0,0 +1,240 @@ +--TEST-- +OPcache static attribute decoded graph tracking follows publication kind +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + [], + 'sparse' => [0 => new CaptureTrackingChild(['seed']), 3 => new CaptureTrackingChild(['gap'])], + ], + new CaptureTrackingChild(), + ); +} + +class CaptureTrackingCachedMethodA +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function value(): CaptureTrackingPayload + { + static $value = null; + + $value ??= capture_tracking_payload('cached_method_a'); + return $value; + } +} + +class CaptureTrackingCachedMethodB +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function value(): CaptureTrackingPayload + { + static $value = null; + + $value ??= capture_tracking_payload('cached_method_b'); + return $value; + } +} + +class CaptureTrackingCachedPropertyA +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?CaptureTrackingPayload $value = null; + + public static function value(): CaptureTrackingPayload + { + self::$value ??= capture_tracking_payload('cached_property_a'); + return self::$value; + } +} + +class CaptureTrackingCachedPropertyB +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?CaptureTrackingPayload $value = null; + + public static function value(): CaptureTrackingPayload + { + self::$value ??= capture_tracking_payload('cached_property_b'); + return self::$value; + } +} + +class CaptureTrackingPersistentMethodA +{ + #[OPcache\PersistentStatic] + public static function value(): CaptureTrackingPayload + { + static $value = null; + + $value ??= capture_tracking_payload('persistent_method_a'); + return $value; + } +} + +class CaptureTrackingPersistentMethodB +{ + #[OPcache\PersistentStatic] + public static function value(): CaptureTrackingPayload + { + static $value = null; + + $value ??= capture_tracking_payload('persistent_method_b'); + return $value; + } +} + +class CaptureTrackingPersistentPropertyA +{ + #[OPcache\PersistentStatic] + public static ?CaptureTrackingPayload $value = null; + + public static function value(): CaptureTrackingPayload + { + self::$value ??= capture_tracking_payload('persistent_property_a'); + return self::$value; + } +} + +class CaptureTrackingPersistentPropertyB +{ + #[OPcache\PersistentStatic] + public static ?CaptureTrackingPayload $value = null; + + public static function value(): CaptureTrackingPayload + { + self::$value ??= capture_tracking_payload('persistent_property_b'); + return self::$value; + } +} + +function capture_tracking_pair(string $kind): array +{ + return match ($kind) { + 'cached_method' => [CaptureTrackingCachedMethodA::value(), CaptureTrackingCachedMethodB::value()], + 'cached_property' => [CaptureTrackingCachedPropertyA::value(), CaptureTrackingCachedPropertyB::value()], + 'persistent_method' => [CaptureTrackingPersistentMethodA::value(), CaptureTrackingPersistentMethodB::value()], + 'persistent_property' => [CaptureTrackingPersistentPropertyA::value(), CaptureTrackingPersistentPropertyB::value()], + default => throw new RuntimeException('unknown kind'), + }; +} + +function capture_tracking_mutate(CaptureTrackingPayload $payload, string $tag): void +{ + $payload->rows['events'][] = $tag; + $payload->rows['sparse'][3]->items[] = $tag; + $payload->child->items[] = $tag; +} + +function capture_tracking_dump(string $kind): void +{ + [$first, $second] = capture_tracking_pair($kind); + echo $kind, ':', + count($first->rows['events']), ',', + count($first->rows['sparse'][3]->items), ',', + count($first->child->items), '|', + count($second->rows['events']), ',', + count($second->rows['sparse'][3]->items), ',', + count($second->child->items), "\n"; +} + +$action = $_GET['action'] ?? 'read'; +$kind = $_GET['kind'] ?? 'cached_method'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + [$first, $second] = capture_tracking_pair($kind); + capture_tracking_mutate($first, 'seed-a'); + capture_tracking_mutate($second, 'seed-b'); + capture_tracking_dump($kind); + return; +} + +if ($action === 'mutate_after_fetch') { + [$first, $second] = capture_tracking_pair($kind); + capture_tracking_mutate($first, 'fetch-a'); + capture_tracking_mutate($second, 'fetch-b'); + capture_tracking_dump($kind); + return; +} + +if ($action === 'read') { + capture_tracking_dump($kind); + return; +} + +throw new RuntimeException('unknown action'); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_capture_tracking_001.php'; +foreach (['cached_method', 'cached_property', 'persistent_method', 'persistent_property'] as $kind) { + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=seed&kind=' . $kind); + echo file_get_contents($base . '?action=mutate_after_fetch&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +cached_method:1,2,1|1,2,1 +cached_method:2,3,2|2,3,2 +cached_method:2,3,2|2,3,2 +reset +cached_property:1,2,1|1,2,1 +cached_property:2,3,2|2,3,2 +cached_property:2,3,2|2,3,2 +reset +persistent_method:1,2,1|1,2,1 +persistent_method:1,2,1|1,2,1 +persistent_method:0,1,0|0,1,0 +reset +persistent_property:1,2,1|1,2,1 +persistent_property:1,2,1|1,2,1 +persistent_property:0,1,0|0,1,0 diff --git a/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt new file mode 100644 index 000000000000..313d26741e42 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt @@ -0,0 +1,103 @@ +--TEST-- +OPcache PersistentStatic class blob survives dynamic method statics with JIT enabled +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,7,6,21,1 +1,6,21,1,1 +11,7,33,1 +1,7,33,1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_class_blob_readonly_skip_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_blob_readonly_skip_001.phpt new file mode 100644 index 000000000000..a7ea7b5f4c37 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_class_blob_readonly_skip_001.phpt @@ -0,0 +1,94 @@ +--TEST-- +OPcache PersistentStatic skips class blob publish for read-only cached requests +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +logFile, "serialize\n", FILE_APPEND); + return ['logFile' => $this->logFile, 'value' => $this->value]; + } + + public function __unserialize(array $data): void + { + $this->logFile = $data['logFile']; + $this->value = $data['value']; + } +} + +#[OPcache\PersistentStatic] +class ReadOnlyBlobState +{ + public static function value(string $logFile): int + { + static $probe = null; + + if ($probe === null) { + $probe = new PublishProbe($logFile, 7); + } + + return $probe->value; + } +} + +$logFile = __DIR__ . '/persistent_static_009.log'; +$action = $_GET['action'] ?? 'value'; + +if ($action === 'reset') { + @unlink($logFile); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'count') { + echo is_file($logFile) ? count(file($logFile, FILE_IGNORE_NEW_LINES)) : 0, "\n"; + return; +} + +echo ReadOnlyBlobState::value($logFile), "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +@unlink(__DIR__ . '/persistent_static_009.log'); + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); + +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_009.php?action=reset'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_009.php?action=value'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_009.php?action=count'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_009.php?action=value'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_009.php?action=count'); + +?> +--CLEAN-- + +--EXPECT-- +reset +7 +1 +7 +1 diff --git a/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt new file mode 100644 index 000000000000..1c375286a70a --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt @@ -0,0 +1,61 @@ +--TEST-- +OPcache PersistentStatic class attribute stores class-wide state under one cache key +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1 +1,10,2,1 +2,21,3 diff --git a/ext/opcache/tests/static_cache_persistent_static_class_property_persistence_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_property_persistence_001.phpt new file mode 100644 index 000000000000..b08738ee1db1 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_class_property_persistence_001.phpt @@ -0,0 +1,51 @@ +--TEST-- +OPcache PersistentStatic persists class static properties across requests +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1,1 +2,2,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_complex_value_persistence_001.phpt b/ext/opcache/tests/static_cache_persistent_static_complex_value_persistence_001.phpt new file mode 100644 index 000000000000..04ccf180b7d5 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_complex_value_persistence_001.phpt @@ -0,0 +1,71 @@ +--TEST-- +OPcache PersistentStatic persists complex static values across requests +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + new CounterBox(100), 'gap' => []]; + $state['gap'][4] = 'seed'; + unset($state['gap'][4]); + } + + $state['box']->value++; + $state['gap'][] = 'tail'; + + return $state['box']->value . ':' . array_key_last($state['gap']); + } +} + +if (ComplexState::$box === null) { + ComplexState::$box = new CounterBox(1); + ComplexState::$gap[3] = 'seed'; + unset(ComplexState::$gap[3]); +} + +ComplexState::$box->value++; +ComplexState::$gap[] = 'tail'; + +echo ComplexState::$box->value, ',', array_key_last(ComplexState::$gap), ',', ComplexState::methodState(), "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32'); + +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_003.php'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_003.php'); + +?> +--CLEAN-- + +--EXPECT-- +2,4,101:5 +3,5,102:6 diff --git a/ext/opcache/tests/static_cache_persistent_static_disabled_without_persistent_cache_memory_001.phpt b/ext/opcache/tests/static_cache_persistent_static_disabled_without_persistent_cache_memory_001.phpt new file mode 100644 index 000000000000..8f1f2a970153 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_disabled_without_persistent_cache_memory_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +OPcache PersistentStatic stays disabled when persistent cache memory is 0 +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1 +1 diff --git a/ext/opcache/tests/static_cache_persistent_static_inherited_attributes_001.phpt b/ext/opcache/tests/static_cache_persistent_static_inherited_attributes_001.phpt new file mode 100644 index 000000000000..6df412bbe026 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_inherited_attributes_001.phpt @@ -0,0 +1,62 @@ +--TEST-- +OPcache class-level static cache attributes are not inherited +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1,1,1 +2,2,1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_invalidate_001.phpt b/ext/opcache/tests/static_cache_persistent_static_invalidate_001.phpt new file mode 100644 index 000000000000..8456d6830471 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_invalidate_001.phpt @@ -0,0 +1,102 @@ +--TEST-- +OPcache PersistentStatic state is deleted only for the invalidated script +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1,1,1 +2,2,2,2 +1 +2 +bool(true) +keep +keep-persistent +1,1,1,1 +3 diff --git a/ext/opcache/tests/static_cache_persistent_static_invalidate_after_access_001.phpt b/ext/opcache/tests/static_cache_persistent_static_invalidate_after_access_001.phpt new file mode 100644 index 000000000000..473787fc02ed --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_invalidate_after_access_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +OPcache PersistentStatic is not republished after opcache_invalidate() in the same request +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1 +2,2 +3,3 +bool(true) +1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_local_copy_array_mutation_publish_001.phpt b/ext/opcache/tests/static_cache_persistent_static_local_copy_array_mutation_publish_001.phpt new file mode 100644 index 000000000000..b79b4d337efe --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_local_copy_array_mutation_publish_001.phpt @@ -0,0 +1,128 @@ +--TEST-- +OPcache PersistentStatic skips nested array local-copy mutation publish +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +logFile, "serialize\n", FILE_APPEND); + return ['logFile' => $this->logFile, 'value' => $this->value]; + } + + public function __unserialize(array $data): void + { + $this->logFile = $data['logFile']; + $this->value = $data['value']; + } +} + +class PersistentStaticLocalCopyArrayState +{ + #[OPcache\PersistentStatic] + public static array $value = []; +} + +function local_copy_log_file(): string +{ + return __DIR__ . '/persistent_static_local_copy_array_mutation_publish_001.log'; +} + +function local_copy_log_count(): int +{ + $logFile = local_copy_log_file(); + return is_file($logFile) ? count(file($logFile, FILE_IGNORE_NEW_LINES)) : 0; +} + +$action = $_GET['action'] ?? 'read'; +$logFile = local_copy_log_file(); + +if ($action === 'reset') { + @unlink($logFile); + OPcache\persistent_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + PersistentStaticLocalCopyArrayState::$value = [ + 'nested' => ['seed'], + 'probe' => new PersistentStaticLocalCopyPublishProbe($logFile, 42), + ]; + echo 'seed-static=', count(PersistentStaticLocalCopyArrayState::$value['nested']), "\n"; + echo 'seed-log=', local_copy_log_count(), "\n"; + return; +} + +if ($action === 'local') { + $copy = PersistentStaticLocalCopyArrayState::$value['nested']; + $copy[] = 'local'; + echo 'local-copy=', count($copy), "\n"; + echo 'local-static=', count(PersistentStaticLocalCopyArrayState::$value['nested']), "\n"; + echo 'local-log=', local_copy_log_count(), "\n"; + return; +} + +if ($action === 'direct') { + PersistentStaticLocalCopyArrayState::$value['nested'][] = 'direct'; + echo 'direct-static=', count(PersistentStaticLocalCopyArrayState::$value['nested']), "\n"; + echo 'direct-log=', local_copy_log_count(), "\n"; + return; +} + +echo 'read-static=', count(PersistentStaticLocalCopyArrayState::$value['nested']), "\n"; +echo 'read-log=', local_copy_log_count(), "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +@unlink(__DIR__ . '/persistent_static_local_copy_array_mutation_publish_001.log'); + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_local_copy_array_mutation_publish_001.php'; +echo file_get_contents($base . '?action=reset'); +echo file_get_contents($base . '?action=seed'); +echo file_get_contents($base . '?action=local'); +echo file_get_contents($base . '?action=read'); +echo file_get_contents($base . '?action=direct'); +echo file_get_contents($base . '?action=read'); + +?> +--CLEAN-- + +--EXPECT-- +reset +seed-static=1 +seed-log=1 +local-copy=2 +local-static=1 +local-log=1 +read-static=1 +read-log=1 +direct-static=2 +direct-log=2 +read-static=2 +read-log=2 diff --git a/ext/opcache/tests/static_cache_persistent_static_method_static_persistence_001.phpt b/ext/opcache/tests/static_cache_persistent_static_method_static_persistence_001.phpt new file mode 100644 index 000000000000..f6bdb65374a6 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_method_static_persistence_001.phpt @@ -0,0 +1,72 @@ +--TEST-- +OPcache PersistentStatic persists method static variables across requests +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,2,1,1 +2,3,1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_object_assignment_fast_path_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_assignment_fast_path_001.phpt new file mode 100644 index 000000000000..858fb5101e7b --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_object_assignment_fast_path_001.phpt @@ -0,0 +1,113 @@ +--TEST-- +OPcache PersistentStatic class object assignments publish a snapshot without following later object mutations +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +name = 'mutated'; + echo 'global-in-request=', PersistentStaticAssignmentFastPathState::$payload->name, "\n"; + return; +} + +if ($action === 'read-global') { + echo 'global-after=', PersistentStaticAssignmentFastPathState::$payload?->name ?? 'missing', "\n"; + return; +} + +if ($action === 'seed-global-property') { + PersistentStaticPropertyAssignmentFastPathState::$payload = new PersistentStaticAssignmentFastPathPayload('assigned'); + PersistentStaticPropertyAssignmentFastPathState::$payload->name = 'mutated'; + echo 'global-property-in-request=', PersistentStaticPropertyAssignmentFastPathState::$payload->name, "\n"; + return; +} + +if ($action === 'read-global-property') { + echo 'global-property-after=', PersistentStaticPropertyAssignmentFastPathState::$payload?->name ?? 'missing', "\n"; + return; +} + +if ($action === 'seed-cached') { + VolatileStaticAssignmentTrackedState::$payload = new PersistentStaticAssignmentFastPathPayload('assigned'); + VolatileStaticAssignmentTrackedState::$payload->name = 'mutated'; + echo 'cached-in-request=', VolatileStaticAssignmentTrackedState::$payload->name, "\n"; + return; +} + +echo 'cached-after=', VolatileStaticAssignmentTrackedState::$payload?->name ?? 'missing', "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_object_assignment_fast_path_001.php'; +echo file_get_contents($base . '?action=reset'); +echo file_get_contents($base . '?action=seed-global'); +echo file_get_contents($base . '?action=read-global'); +echo file_get_contents($base . '?action=reset'); +echo file_get_contents($base . '?action=seed-global-property'); +echo file_get_contents($base . '?action=read-global-property'); +echo file_get_contents($base . '?action=reset'); +echo file_get_contents($base . '?action=seed-cached'); +echo file_get_contents($base . '?action=read-cached'); + +?> +--CLEAN-- + +--EXPECT-- +reset +global-in-request=mutated +global-after=assigned +reset +global-property-in-request=mutated +global-property-after=assigned +reset +cached-in-request=mutated +cached-after=mutated diff --git a/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt new file mode 100644 index 000000000000..ed2a25b1fbc7 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +OPcache PersistentStatic snapshots object assignments without following object property writes +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=32 +opcache.optimization_level=0 +opcache.file_update_protection=0 +opcache.jit=0 +--FILE-- + 1]; +var_dump(NestedObjectPropertyState::$propertyState->count); +var_dump(OPcache\persistent_cache_info()['entry_count']); + +NestedObjectPropertyState::$propertyState->count++; +var_dump(NestedObjectPropertyState::$propertyState->count); +var_dump(OPcache\persistent_cache_info()['entry_count']); + +NestedObjectPropertyState::$propertyState->count = 10; +var_dump(NestedObjectPropertyState::$propertyState->count); +var_dump(OPcache\persistent_cache_info()['entry_count']); + +?> +--EXPECT-- +int(1) +int(1) +int(2) +int(1) +int(10) +int(1) diff --git a/ext/opcache/tests/static_cache_persistent_static_object_dim_mutation_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_dim_mutation_001.phpt new file mode 100644 index 000000000000..5e5186b3dd06 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_object_dim_mutation_001.phpt @@ -0,0 +1,110 @@ +--TEST-- +OPcache static attributes handle ArrayObject dimension writes by publication kind +--EXTENSIONS-- +opcache +spl +--CONFLICTS-- +server +--FILE-- + 0, 'events' => [], 'remove' => 'seed']); + return $object; + } +} + +class PersistentStaticArrayObjectMethodState +{ + #[OPcache\PersistentStatic] + public static function value(): ArrayObject + { + static $object = null; + + $object ??= new ArrayObject(['count' => 0, 'events' => [], 'remove' => 'seed']); + return $object; + } +} + +class PersistentStaticArrayObjectPropertyState +{ + #[OPcache\PersistentStatic] + public static ?ArrayObject $object = null; + + public static function value(): ArrayObject + { + self::$object ??= new ArrayObject(['count' => 0, 'events' => [], 'remove' => 'seed']); + return self::$object; + } +} + +$action = $_GET['action'] ?? 'read'; +$backend = $_GET['backend'] ?? 'volatile_static'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +$object = match ($backend) { + 'volatile_static' => VolatileStaticArrayObjectMethodState::value(), + 'persistent_static' => PersistentStaticArrayObjectMethodState::value(), + 'persistent_static_property' => PersistentStaticArrayObjectPropertyState::value(), + default => throw new RuntimeException('unknown backend'), +}; + +if ($action === 'mutate') { + $object['count'] += 1; + $object['events'] = ['x']; + unset($object['remove']); +} + +echo $backend, '=', (int) $object['count'], ',', count($object['events']), ',', ($object->offsetExists('remove') ? 1 : 0), "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_object_dim_mutation_001.php'; +foreach (['volatile_static', 'persistent_static', 'persistent_static_property'] as $backend) { + $query = 'backend=' . $backend; + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=read&' . $query); + echo file_get_contents($base . '?action=mutate&' . $query); + echo file_get_contents($base . '?action=read&' . $query); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +volatile_static=0,0,1 +volatile_static=1,1,0 +volatile_static=1,1,0 +reset +persistent_static=0,0,1 +persistent_static=1,1,0 +persistent_static=0,0,1 +reset +persistent_static_property=0,0,1 +persistent_static_property=1,1,0 +persistent_static_property=0,0,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_object_property_mutation_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_property_mutation_001.phpt new file mode 100644 index 000000000000..13c167e2555a --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_object_property_mutation_001.phpt @@ -0,0 +1,171 @@ +--TEST-- +OPcache static attributes handle object property array writes by publication kind +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + VolatileStaticPlainMethodState::value(), + 'volatile_static:safe_direct' => VolatileStaticSafeDirectMethodState::value(), + 'persistent_static:plain' => PersistentStaticPlainMethodState::value(), + 'persistent_static:safe_direct' => PersistentStaticSafeDirectMethodState::value(), + 'persistent_static_property:plain' => PersistentStaticPlainPropertyState::value(), + 'persistent_static_property:safe_direct' => PersistentStaticSafeDirectPropertyState::value(), + default => throw new RuntimeException('unknown backend or kind'), +}; + +if ($action === 'mutate') { + $object->bag[] = 'x'; +} + +echo $backend, '/', $kind, '=', count($object->bag), "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_object_property_mutation_001.php'; +foreach (['volatile_static', 'persistent_static', 'persistent_static_property'] as $backend) { + foreach (['plain', 'safe_direct'] as $kind) { + $query = 'backend=' . $backend . '&kind=' . $kind; + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=read&' . $query); + echo file_get_contents($base . '?action=mutate&' . $query); + echo file_get_contents($base . '?action=read&' . $query); + } +} + +?> +--CLEAN-- + +--EXPECT-- +reset +volatile_static/plain=0 +volatile_static/plain=1 +volatile_static/plain=1 +reset +volatile_static/safe_direct=0 +volatile_static/safe_direct=1 +volatile_static/safe_direct=1 +reset +persistent_static/plain=0 +persistent_static/plain=1 +persistent_static/plain=0 +reset +persistent_static/safe_direct=0 +persistent_static/safe_direct=1 +persistent_static/safe_direct=0 +reset +persistent_static_property/plain=0 +persistent_static_property/plain=1 +persistent_static_property/plain=0 +reset +persistent_static_property/safe_direct=0 +persistent_static_property/safe_direct=1 +persistent_static_property/safe_direct=0 diff --git a/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt b/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt new file mode 100644 index 000000000000..6ffff4972b54 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt @@ -0,0 +1,71 @@ +--TEST-- +OPcache PersistentStatic publishes property array mutations immediately +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + [1]]; + NestedSnapshotPropertyState::$propertyState['numbers'][] = 2; + echo 'seed=', implode(',', NestedSnapshotPropertyState::$propertyState['numbers']), "\n"; + echo 'entries=', OPcache\persistent_cache_info()['entry_count'], "\n"; + return; +} + +if ($action === 'mutate') { + NestedSnapshotPropertyState::$propertyState['numbers'][] = 3; + echo 'mutate=', implode(',', NestedSnapshotPropertyState::$propertyState['numbers']), "\n"; + return; +} + +echo 'read=', implode(',', NestedSnapshotPropertyState::$propertyState['numbers'] ?? []), "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_004.php'; +echo file_get_contents($base . '?action=reset'); +echo file_get_contents($base . '?action=seed'); +echo file_get_contents($base . '?action=read'); +echo file_get_contents($base . '?action=mutate'); +echo file_get_contents($base . '?action=read'); + +?> +--CLEAN-- + +--EXPECT-- +reset +seed=1,2 +entries=1 +read=1,2 +mutate=1,2,3 +read=1,2,3 diff --git a/ext/opcache/tests/static_cache_persistent_static_readonly_publish_001.phpt b/ext/opcache/tests/static_cache_persistent_static_readonly_publish_001.phpt new file mode 100644 index 000000000000..0af6ed85bb4f --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_readonly_publish_001.phpt @@ -0,0 +1,175 @@ +--TEST-- +OPcache VolatileStatic tracking skips read-only shutdown publish like PersistentStatic +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +logFile, "serialize\n", FILE_APPEND); + return ['logFile' => $this->logFile, 'value' => $this->value]; + } + + public function __unserialize(array $data): void + { + $this->logFile = $data['logFile']; + $this->value = $data['value']; + } +} + +class CachedReadOnlyMethodState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function value(string $logFile): int + { + static $probe = null; + + if ($probe === null) { + $probe = new ReadOnlyPublishProbe($logFile, 11); + } + + return $probe->value; + } +} + +class PersistentReadOnlyMethodState +{ + #[OPcache\PersistentStatic] + public static function value(string $logFile): int + { + static $probe = null; + + if ($probe === null) { + $probe = new ReadOnlyPublishProbe($logFile, 13); + } + + return $probe->value; + } +} + +class CachedReadOnlyPropertyState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?ReadOnlyPublishProbe $probe = null; + + public static function value(string $logFile): int + { + self::$probe ??= new ReadOnlyPublishProbe($logFile, 17); + return self::$probe->value; + } +} + +class PersistentReadOnlyPropertyState +{ + #[OPcache\PersistentStatic] + public static ?ReadOnlyPublishProbe $probe = null; + + public static function value(string $logFile): int + { + self::$probe ??= new ReadOnlyPublishProbe($logFile, 19); + return self::$probe->value; + } +} + +function readonly_publish_log_file(string $kind): string +{ + return __DIR__ . '/persistent_static_readonly_publish_001_' . $kind . '.log'; +} + +$kinds = [ + 'cached_method', + 'cached_property', + 'persistent_method', + 'persistent_property', +]; +$action = $_GET['action'] ?? 'value'; +$kind = $_GET['kind'] ?? 'cached_method'; + +if ($action === 'reset') { + foreach ($kinds as $logKind) { + @unlink(readonly_publish_log_file($logKind)); + } + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +$logFile = readonly_publish_log_file($kind); +if ($action === 'count') { + echo $kind, '_count=', is_file($logFile) ? count(file($logFile, FILE_IGNORE_NEW_LINES)) : 0, "\n"; + return; +} + +$value = match ($kind) { + 'cached_method' => CachedReadOnlyMethodState::value($logFile), + 'cached_property' => CachedReadOnlyPropertyState::value($logFile), + 'persistent_method' => PersistentReadOnlyMethodState::value($logFile), + 'persistent_property' => PersistentReadOnlyPropertyState::value($logFile), + default => throw new RuntimeException('Unknown kind: ' . $kind), +}; + +echo $kind, '=', $value, "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +foreach (['cached_method', 'cached_property', 'persistent_method', 'persistent_property'] as $kind) { + @unlink(__DIR__ . '/persistent_static_readonly_publish_001_' . $kind . '.log'); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_readonly_publish_001.php'; +echo file_get_contents($base . '?action=reset'); +foreach (['cached_method', 'cached_property', 'persistent_method', 'persistent_property'] as $kind) { + echo file_get_contents($base . '?action=value&kind=' . $kind); + echo file_get_contents($base . '?action=count&kind=' . $kind); + echo file_get_contents($base . '?action=value&kind=' . $kind); + echo file_get_contents($base . '?action=count&kind=' . $kind); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +cached_method=11 +cached_method_count=1 +cached_method=11 +cached_method_count=1 +cached_property=17 +cached_property_count=1 +cached_property=17 +cached_property_count=1 +persistent_method=13 +persistent_method_count=1 +persistent_method=13 +persistent_method_count=1 +persistent_property=19 +persistent_property_count=1 +persistent_property=19 +persistent_property_count=1 diff --git a/ext/opcache/tests/static_cache_persistent_static_recursive_shared_graph_snapshot_001.phpt b/ext/opcache/tests/static_cache_persistent_static_recursive_shared_graph_snapshot_001.phpt new file mode 100644 index 000000000000..43f2fae5d74f --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_recursive_shared_graph_snapshot_001.phpt @@ -0,0 +1,76 @@ +--TEST-- +OPcache PersistentStatic snapshots recursive shared-graph state across requests +--EXTENSIONS-- +opcache +spl +--CONFLICTS-- +server +--FILE-- + new GraphNode(new GraphChild(1), ['hits' => []]), + 'serial' => new ArrayObject([ + 'box' => new GraphChild(10), + 'nested' => ['hits' => []], + ]), + ]; +} + +$state = &RecursivePropertyState::$state; +$state['graph']->child->count++; +$state['graph']->trail['hits'][] = $state['graph']->child->count; + +$serial = $state['serial']; +$serial['box']->count++; +$nested = $serial['nested']; +$nested['hits'][] = $serial['box']->count; +$serial['nested'] = $nested; + +echo $state['graph']->child->count, ',', count($state['graph']->trail['hits']), ',', $serial['box']->count, ',', count($serial['nested']['hits']), "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_011.php'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_011.php'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_011.php'); + +?> +--CLEAN-- + +--EXPECT-- +2,1,11,1 +2,1,11,1 +2,1,11,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_reset_001.phpt b/ext/opcache/tests/static_cache_persistent_static_reset_001.phpt new file mode 100644 index 000000000000..0479b9bb1195 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_reset_001.phpt @@ -0,0 +1,83 @@ +--TEST-- +OPcache PersistentStatic and explicit static caches are deleted by opcache_reset() +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1,1,1 +2,2,2,2 +bool(true) +missing-volatile +missing-persistent +1,1,1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_reset_after_access_001.phpt b/ext/opcache/tests/static_cache_persistent_static_reset_after_access_001.phpt new file mode 100644 index 000000000000..006b1b05031b --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_reset_after_access_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +OPcache PersistentStatic is not republished after opcache_reset() in the same request +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1 +2,2 +3,3 +bool(true) +1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt b/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt new file mode 100644 index 000000000000..af6b64e2258c --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt @@ -0,0 +1,117 @@ +--TEST-- +OPcache PersistentStatic storage failures throw StaticCacheException +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.persistent_size_mb=8 +opcache.optimization_level=0 +opcache.file_update_protection=0 +opcache.jit=0 +--FILE-- +getMessage(), "\n"; + } +} + +class PersistentStaticStoreFailureProperty +{ + #[OPcache\PersistentStatic] + public static mixed $value = null; +} + +#[OPcache\PersistentStatic] +class PersistentStaticStoreFailureClass +{ + public static mixed $value = null; +} + +class PersistentStaticStoreFailureMethod +{ + #[OPcache\PersistentStatic] + public static function assign(mixed $value): void + { + static $state = null; + + $state = $value; + } +} + +class PersistentStaticStoreFailureArray +{ + #[OPcache\PersistentStatic] + public static array $value = []; +} + +class PersistentStaticStoreFailureUnsupportedValueBox +{ + public function __construct(public mixed $value) + { + } +} + +OPcache\persistent_clear(); + +dump_static_cache_exception('property-resource', function (): void { + $resource = fopen('/dev/null', 'r'); + try { + PersistentStaticStoreFailureProperty::$value = $resource; + } finally { + if (is_resource($resource)) { + fclose($resource); + } + } +}); + +dump_static_cache_exception('property-closure', function (): void { + PersistentStaticStoreFailureProperty::$value = static fn () => null; +}); + +dump_static_cache_exception('property-object-resource', function (): void { + $resource = fopen('/dev/null', 'r'); + try { + PersistentStaticStoreFailureProperty::$value = new PersistentStaticStoreFailureUnsupportedValueBox($resource); + } finally { + if (is_resource($resource)) { + fclose($resource); + } + } +}); + +dump_static_cache_exception('property-object-closure', function (): void { + PersistentStaticStoreFailureProperty::$value = new PersistentStaticStoreFailureUnsupportedValueBox(static fn () => null); +}); + +$unused = PersistentStaticStoreFailureClass::$value; +dump_static_cache_exception('class-closure', function (): void { + PersistentStaticStoreFailureClass::$value = static fn () => null; +}); + +dump_static_cache_exception('method-closure', function (): void { + PersistentStaticStoreFailureMethod::assign(static fn () => null); +}); + +PersistentStaticStoreFailureArray::$value = []; +dump_static_cache_exception('array-mutation-overflow', function (): void { + PersistentStaticStoreFailureArray::$value[] = str_repeat('X', 12 * 1024 * 1024); +}); + +OPcache\persistent_clear(); + +?> +--EXPECT-- +property-resource: OPcache\StaticCacheException: resources cannot be stored in the static cache +property-closure: OPcache\StaticCacheException: Closure objects cannot be stored in the static cache +property-object-resource: OPcache\StaticCacheException: resources cannot be stored in the static cache +property-object-closure: OPcache\StaticCacheException: Closure objects cannot be stored in the static cache +class-closure: OPcache\StaticCacheException: Closure objects cannot be stored in the static cache +method-closure: OPcache\StaticCacheException: Closure objects cannot be stored in the static cache +array-mutation-overflow: OPcache\StaticCacheException: not enough shared memory left diff --git a/ext/opcache/tests/static_cache_persistent_static_timestamp_invalidate_001.phpt b/ext/opcache/tests/static_cache_persistent_static_timestamp_invalidate_001.phpt new file mode 100644 index 000000000000..3990d880a945 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_timestamp_invalidate_001.phpt @@ -0,0 +1,53 @@ +--TEST-- +OPcache PersistentStatic state is discarded when a cached class definition changes +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +v1:1 +v1:2 +v2:1 diff --git a/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt b/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt new file mode 100644 index 000000000000..05d970de4407 --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt @@ -0,0 +1,302 @@ +--TEST-- +OPcache PersistentStatic class, property, and method state handles cross-thread writes on ZTS builds +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- + MAX_COMMAND_OUTPUT_BYTES) { + return substr($contents, 0, MAX_COMMAND_OUTPUT_BYTES) . "\n...[output truncated]\n"; + } + + return $contents; +} + +function runCommand(array $command): array +{ + $stdoutFile = tempnam(sys_get_temp_dir(), 'opcache-zts-out-'); + $stderrFile = tempnam(sys_get_temp_dir(), 'opcache-zts-err-'); + if ($stdoutFile === false || $stderrFile === false) { + throw new RuntimeException('failed to create temporary files'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['file', $stdoutFile, 'w'], + 2 => ['file', $stderrFile, 'w'], + ]; + + $process = proc_open($command, $descriptorSpec, $pipes); + if (!is_resource($process)) { + @unlink($stdoutFile); + @unlink($stderrFile); + throw new RuntimeException('proc_open failed'); + } + + fclose($pipes[0]); + $status = proc_close($process); + + $stdout = readCommandOutput($stdoutFile); + $stderr = readCommandOutput($stderrFile); + @unlink($stdoutFile); + @unlink($stderrFile); + + return [$status, $stdout, $stderr]; +} + +function parseBuildFlags(string $flags): array +{ + $tokens = preg_split('/\s+/', trim($flags)); + if ($tokens === false) { + return []; + } + + return array_values(array_filter($tokens, static function (string $token): bool { + return $token !== '' && !str_contains($token, '$('); + })); +} + +function resolveBuildFlags(string $buildRoot, array $variables): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + $flags = []; + + if ($makefile === false) { + return $flags; + } + + foreach ($variables as $variable) { + if (preg_match('/^' . preg_quote($variable, '/') . '\s*=\s*(.+)$/m', $makefile, $matches)) { + $flags = array_merge($flags, parseBuildFlags($matches[1])); + } + } + + return $flags; +} + +function resolveBuildCommand(string $buildRoot, string $variable, array $fallback): array +{ + $value = getenv($variable); + if (is_string($value) && $value !== '') { + $tokens = parseBuildFlags($value); + if ($tokens !== []) { + return $tokens; + } + } + + $tokens = resolveBuildFlags($buildRoot, [$variable]); + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveBuildRoot(string $root): string +{ + $buildRoot = null; + $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; + if ($php) { + $php = realpath($php); + if ($php !== false) { + if (PHP_OS_FAMILY === 'Windows') { + $candidate = dirname($php); + if (glob($candidate . '/php*embed.lib')) { + $buildRoot = $candidate; + } + } else { + $candidate = dirname($php, 3); + if (file_exists($candidate . '/.libs/libphp.a') || file_exists($candidate . '/libs/libphp.a')) { + $buildRoot = $candidate; + } + } + } + } + + if ($buildRoot === null && PHP_OS_FAMILY !== 'Windows' && (file_exists($root . '/.libs/libphp.a') || file_exists($root . '/libs/libphp.a'))) { + $buildRoot = $root; + } + + if ($buildRoot === null) { + throw new RuntimeException('Failed to resolve build root'); + } + + return $buildRoot; +} + +function resolveExtraLibs(string $buildRoot): array +{ + if (PHP_OS_FAMILY === 'Windows') { + return []; + } + + $makefile = @file_get_contents($buildRoot . '/Makefile'); + if ($makefile !== false && preg_match('/^EXTRA_LIBS\s*=\s*(.+)$/m', $makefile, $matches)) { + $libs = preg_split('/\s+/', trim($matches[1])); + if ($libs !== false && $libs !== ['']) { + return $libs; + } + } + + return [ + '-lm', + '-lpthread', + '-ldl', + '-lresolv', + '-lutil', + ]; +} + +function resolveWindowsEmbedLib(string $buildRoot): string +{ + $candidates = glob($buildRoot . '/php*embed.lib'); + if ($candidates !== false && $candidates !== []) { + return $candidates[0]; + } + + throw new RuntimeException('Failed to resolve Windows embed library'); +} + +$root = realpath(__DIR__ . '/../../..'); +if ($root === false) { + throw new RuntimeException('Failed to resolve source root'); +} + +$buildRoot = resolveBuildRoot($root); +$source = __DIR__ . '/helpers/persistent_static_zts_threads_001.c'; +$binary = __DIR__ . '/helpers/persistent_static_zts_threads_001' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); +$scenario = __DIR__ . '/helpers/persistent_static_zts_threads_001.inc'; +$compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$extraLibs = resolveExtraLibs($buildRoot); + +if (PHP_OS_FAMILY === 'Windows') { + $compileCommand = [ + ...$compiler, + '/nologo', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/DHAVE_CONFIG_H', + '/I' . $buildRoot, + '/I' . $buildRoot . '/main', + '/I' . $buildRoot . '/Zend', + '/I' . $buildRoot . '/TSRM', + '/I' . $buildRoot . '/sapi/embed', + '/I' . $root, + '/I' . $root . '/main', + '/I' . $root . '/Zend', + '/I' . $root . '/TSRM', + '/I' . $root . '/sapi/embed', + '/Fe:' . $binary, + $source, + '/link', + resolveWindowsEmbedLib($buildRoot), + ]; +} else { + $compileCommand = [ + ...$compiler, + ...$buildFlags, + '-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '-DHAVE_CONFIG_H', + '-I' . $buildRoot, + '-I' . $buildRoot . '/main', + '-I' . $buildRoot . '/Zend', + '-I' . $buildRoot . '/TSRM', + '-I' . $buildRoot . '/sapi/embed', + '-I' . $root, + '-I' . $root . '/main', + '-I' . $root . '/Zend', + '-I' . $root . '/TSRM', + '-I' . $root . '/sapi/embed', + '-o', + $binary, + $source, + $root . '/sapi/embed/php_embed.c', + file_exists($buildRoot . '/.libs/libphp.a') ? $buildRoot . '/.libs/libphp.a' : $buildRoot . '/libs/libphp.a', + ]; + $compileCommand = array_merge($compileCommand, $extraLibs); +} + +[$status, $stdout, $stderr] = runCommand($compileCommand); +if ($status !== 0) { + echo $stdout, $stderr; + exit(1); +} + +@chmod($binary, 0755); +[$status, $stdout, $stderr] = runCommand([$binary, $scenario]); +echo $stdout; +if ($status !== 0) { + echo $stderr; + exit(1); +} + +?> +--CLEAN-- + +--EXPECT-- +ok diff --git a/ext/opcache/tests/static_cache_preload_001.inc b/ext/opcache/tests/static_cache_preload_001.inc new file mode 100644 index 000000000000..0578cbf21703 --- /dev/null +++ b/ext/opcache/tests/static_cache_preload_001.inc @@ -0,0 +1,25 @@ + true]; + + public static function next(): int + { + static $value = 0; + + return ++$value; + } +} + +class StaticCachePreloadMethodState +{ + #[OPcache\VolatileStatic] + public static function value(): array + { + static $value = ['preload' => true]; + + return $value; + } +} diff --git a/ext/opcache/tests/static_cache_preload_001.phpt b/ext/opcache/tests/static_cache_preload_001.phpt new file mode 100644 index 000000000000..990e70f7620e --- /dev/null +++ b/ext/opcache/tests/static_cache_preload_001.phpt @@ -0,0 +1,32 @@ +--TEST-- +OPcache static cache attributes work with preloaded classes +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +opcache.preload={PWD}/static_cache_preload_001.inc +--FILE-- + +--EXPECT-- +array(1) { + ["preload"]=> + bool(true) +} +int(1) +array(1) { + ["preload"]=> + bool(true) +} diff --git a/ext/opcache/tests/static_cache_startup_failure_001.phpt b/ext/opcache/tests/static_cache_startup_failure_001.phpt new file mode 100644 index 000000000000..fea81428e60c --- /dev/null +++ b/ext/opcache/tests/static_cache_startup_failure_001.phpt @@ -0,0 +1,63 @@ +--TEST-- +OPcache static cache disables the subsystem after startup failure +--EXTENSIONS-- +opcache +--ENV-- +OPCACHE_STATIC_CACHE_FORCE_STARTUP_FAILURE=1 +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +getMessage(), "\n"; +} + +try { + OPcache\persistent_store('key', 'value'); +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +bool(false) +bool(false) +bool(true) +bool(false) +int(33554432) +bool(false) +bool(false) +bool(true) +bool(false) +int(33554432) +bool(true) +bool(true) +string(42) "Unable to initialize shared memory backend" +string(42) "Unable to initialize shared memory backend" +OPcache\StaticCacheException: Unable to initialize shared memory backend +OPcache\StaticCacheException: Unable to initialize shared memory backend diff --git a/ext/opcache/tests/static_cache_volatile_cache_allocator_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_allocator_001.phpt new file mode 100644 index 000000000000..4ac630110d1c --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_allocator_001.phpt @@ -0,0 +1,39 @@ +--TEST-- +OPcache volatile cache reuses freed payload memory +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +int(2400000) +bool(true) +bool(true) +bool(true) +int(2400000) diff --git a/ext/opcache/tests/static_cache_volatile_cache_allocator_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_allocator_002.phpt new file mode 100644 index 000000000000..bd1c38159888 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_allocator_002.phpt @@ -0,0 +1,33 @@ +--TEST-- +OPcache volatile cache coalesces adjacent freed payload blocks +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +int(3500000) diff --git a/ext/opcache/tests/static_cache_volatile_cache_basic_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_basic_001.phpt new file mode 100644 index 000000000000..1e9fcdc51a2e --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_basic_001.phpt @@ -0,0 +1,48 @@ +--TEST-- +OPcache volatile cache basic KV operations +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + 1, 'y' => true])); +var_dump(OPcache\volatile_fetch('a')); + +var_dump(OPcache\volatile_store_array(['foo' => 'bar', 'baz' => true])); +var_dump(OPcache\volatile_fetch('foo')); +var_dump(OPcache\volatile_fetch('baz')); + +OPcache\volatile_delete_array(['foo', 'baz']); +var_dump(OPcache\volatile_fetch('foo', 'fallback')); + +OPcache\volatile_clear(); +var_dump(OPcache\volatile_fetch('n', 'fallback')); + +?> +--EXPECT-- +bool(true) +int(41) +bool(true) +string(3) "php" +bool(true) +array(2) { + ["x"]=> + int(1) + ["y"]=> + bool(true) +} +bool(true) +string(3) "bar" +bool(true) +string(8) "fallback" +string(8) "fallback" diff --git a/ext/opcache/tests/static_cache_volatile_cache_combined_delete_clear_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_combined_delete_clear_001.phpt new file mode 100644 index 000000000000..51fd5764c292 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_combined_delete_clear_001.phpt @@ -0,0 +1,111 @@ +--TEST-- +OPcache volatile cache combined entries survive delete and clear without corrupting fetched values +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +--FILE-- + $i, + 'text' => str_repeat($prefix, 64), + 'nested' => ['value' => $i * $multiplier], + ]; + } + + return $rows; +} + +function build_graph(string $prefix, int $multiplier): CombinedDeleteClearNode +{ + return new CombinedDeleteClearNode(build_rows($prefix, $multiplier)); +} + +function build_refs(): array +{ + $payload = ['value' => str_repeat('R', 256)]; + $payload['alias'] =& $payload['value']; + + return $payload; +} + +OPcache\volatile_clear(); + +var_dump(OPcache\volatile_store('keep-string', str_repeat('S', 1200000))); +var_dump(OPcache\volatile_store('victim-graph', build_graph('O', 3))); +var_dump(OPcache\volatile_store('keep-refs', build_refs())); + +$fetched = OPcache\volatile_fetch('victim-graph'); +OPcache\volatile_delete('victim-graph'); + +var_dump(OPcache\volatile_fetch('victim-graph', 'missing')); +var_dump($fetched instanceof CombinedDeleteClearNode); +var_dump($fetched->rows[123]['text']); + +$fetched->rows[123]['text'] = 'local-after-delete'; +var_dump($fetched->rows[123]['text']); + +var_dump(OPcache\volatile_store('replacement-graph', build_graph('N', 7))); + +$replacement = OPcache\volatile_fetch('replacement-graph'); +$refs = OPcache\volatile_fetch('keep-refs'); +$refs['alias'] = 'mutated-via-alias'; + +var_dump($replacement->rows[123]['text']); +var_dump($replacement->rows[123]['nested']['value']); +var_dump($refs['value']); + +$cleared = OPcache\volatile_fetch('replacement-graph'); +OPcache\volatile_clear(); + +var_dump(OPcache\volatile_fetch('keep-string', 'missing')); +var_dump(OPcache\volatile_fetch('replacement-graph', 'missing')); +var_dump($cleared->rows[321]['text']); + +$cleared->rows[321]['text'] = 'local-after-clear'; +var_dump($cleared->rows[321]['text']); + +var_dump(OPcache\volatile_store('after-clear-string', str_repeat('A', 1200000))); +var_dump(OPcache\volatile_store('after-clear-graph', build_graph('C', 11))); + +$afterClear = OPcache\volatile_fetch('after-clear-graph'); + +var_dump(strlen(OPcache\volatile_fetch('after-clear-string'))); +var_dump($afterClear->rows[321]['text']); +var_dump($afterClear->rows[321]['nested']['value']); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +string(7) "missing" +bool(true) +string(64) "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" +string(18) "local-after-delete" +bool(true) +string(64) "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN" +int(861) +string(17) "mutated-via-alias" +string(7) "missing" +string(7) "missing" +string(64) "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN" +string(17) "local-after-clear" +bool(true) +bool(true) +int(1200000) +string(64) "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" +int(3531) diff --git a/ext/opcache/tests/static_cache_volatile_cache_complex_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_complex_001.phpt new file mode 100644 index 000000000000..c44d157a37c2 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_complex_001.phpt @@ -0,0 +1,115 @@ +--TEST-- +OPcache volatile cache complex values use native encoding with lossless fallback +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- +id = $id; + $this->name = $name; + } + + public function __serialize(): array + { + self::$serializeCount++; + + return ['id' => $this->id, 'name' => $this->name]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCount++; + $this->id = $data['id']; + $this->name = $data['name']; + } + + public function info(): string + { + return $this->id . ':' . $this->name; + } +} + +$gap = []; +$gap[4] = 'seed'; +unset($gap[4]); + +$payload = [ + 'nested' => ['alpha' => [1, 2, 3], 'beta' => ['x' => 'y']], + 'props' => new SimpleUser('Alice', 30), + 'serialize' => new SerUser(7, 'Bob'), + 'internal' => new DateTimeImmutable('2026-06-15 09:30:00', new DateTimeZone('UTC')), + 'gap' => $gap, +]; + +var_dump(OPcache\volatile_store('complex', $payload)); + +$fetched = OPcache\volatile_fetch('complex'); +var_dump($fetched['props'] instanceof SimpleUser); +var_dump($fetched['props']->name); +var_dump($fetched['props']->age); +var_dump($fetched['serialize'] instanceof SerUser); +var_dump($fetched['serialize']->info()); +var_dump(SerUser::$serializeCount); +var_dump(SerUser::$unserializeCount); +var_dump($fetched['internal'] instanceof DateTimeImmutable); +var_dump($fetched['internal']->format('Y-m-d H:i:s')); + +$fetched['gap'][] = 'tail'; +var_dump(array_key_last($fetched['gap'])); + +$shared = new stdClass(); +$shared->value = 42; +var_dump(OPcache\volatile_store('shared_pair', [$shared, $shared])); + +$pair = OPcache\volatile_fetch('shared_pair'); +var_dump($pair[0] instanceof stdClass); +var_dump(spl_object_id($pair[0]) === spl_object_id($pair[1])); + +$refs = ['value' => 1]; +$refs['alias'] =& $refs['value']; +var_dump(OPcache\volatile_store('refs', $refs)); + +$fetchedRefs = OPcache\volatile_fetch('refs'); +$fetchedRefs['alias'] = 7; +var_dump($fetchedRefs['value']); + +?> +--EXPECT-- +bool(true) +bool(true) +string(5) "Alice" +int(30) +bool(true) +string(5) "7:Bob" +int(1) +int(1) +bool(true) +string(19) "2026-06-15 09:30:00" +int(5) +bool(true) +bool(true) +bool(true) +bool(true) +int(7) diff --git a/ext/opcache/tests/static_cache_volatile_cache_destructive_mutators_retire_shared_graph_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_destructive_mutators_retire_shared_graph_001.phpt new file mode 100644 index 000000000000..06bdecdf2adf --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_destructive_mutators_retire_shared_graph_001.phpt @@ -0,0 +1,139 @@ +--TEST-- +OPcache volatile cache shared graph survives destructive mutators and reuse +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $i, + 'text' => str_repeat($prefix, 64), + 'nested' => ['value' => $i * $multiplier], + ]; + } + + return $rows; +} + +function wait_for_file(string $path): void +{ + $deadline = microtime(true) + 5.0; + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + usleep(1000); + } +} + +function run_destructive_mutator(string $operation): void +{ + $key = 'destructive_mutator_retire_' . $operation . '_' . getmypid(); + $overwriteKey = 'destructive_mutator_retire_overwrite_' . $operation . '_' . getmypid(); + $prefix = sys_get_temp_dir() . '/opcache_destructive_mutator_retire_' . getmypid() . '_' . $operation; + $readyFile = $prefix . '.ready'; + $doneFile = $prefix . '.done'; + $resultFile = $prefix . '.result'; + @unlink($readyFile); + @unlink($doneFile); + @unlink($resultFile); + + OPcache\volatile_clear(); + if (!OPcache\volatile_store($key, new DestructiveMutatorPayload(build_rows('T', 3)))) { + throw new RuntimeException('store failed'); + } + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + $fetched = OPcache\volatile_fetch($key); + file_put_contents($readyFile, 'ready'); + wait_for_file($doneFile); + + $before = $fetched->rows[123]['text']; + $fetched->rows[123]['text'] = 'changed after ' . $operation; + $after = $fetched->rows[123]['text']; + $nested = $fetched->rows[123]['nested']['value']; + $refetch = OPcache\volatile_fetch($key, 'MISS') === 'MISS' ? 'MISS' : 'HIT'; + + file_put_contents($resultFile, $before . "\n" . $after . "\n" . $nested . "\n" . $refetch); + exit(0); + } + + wait_for_file($readyFile); + switch ($operation) { + case 'delete': + OPcache\volatile_delete($key); + break; + case 'clear': + OPcache\volatile_clear(); + break; + case 'reset': + opcache_reset(); + break; + } + + if (!OPcache\volatile_store($overwriteKey, new DestructiveMutatorPayload(build_rows('X', 7)))) { + throw new RuntimeException('overwrite store failed'); + } + file_put_contents($doneFile, 'done'); + pcntl_waitpid($pid, $status); + + echo $operation, "\n", file_get_contents($resultFile), "\n"; + + @unlink($readyFile); + @unlink($doneFile); + @unlink($resultFile); +} + +run_destructive_mutator('delete'); +run_destructive_mutator('clear'); +run_destructive_mutator('reset'); + +?> +--EXPECT-- +delete +TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT +changed after delete +369 +MISS +clear +TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT +changed after clear +369 +MISS +reset +TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT +changed after reset +369 +MISS +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_001.phpt new file mode 100644 index 000000000000..be70c3dc6095 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_001.phpt @@ -0,0 +1,191 @@ +--TEST-- +OPcache __DirectCacheSafe uses a direct DateTime path for safe subclasses and keeps fallback for wakeup hooks +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- +getAttributes(OPcache\__DirectCacheSafe::class))); +var_dump(count((new ReflectionClass(DateTimeImmutable::class))->getAttributes(OPcache\__DirectCacheSafe::class))); + +$immutable = new DateTimeImmutable('2026-06-15 11:00:00.111111', new DateTimeZone('UTC')); + +var_dump(OPcache\volatile_store('immutable_datetime', $immutable)); + +$immutableCopy = OPcache\volatile_fetch('immutable_datetime'); +var_dump($immutableCopy instanceof DateTimeImmutable); +var_dump($immutableCopy->format('Y-m-d H:i:s.u e')); + +$offsetImmutable = new DateTimeImmutable('2023-10-27 10:00:00 +05:30'); + +var_dump(OPcache\volatile_store('offset_immutable_datetime', $offsetImmutable)); + +$offsetImmutableCopy = OPcache\volatile_fetch('offset_immutable_datetime'); +var_dump($offsetImmutableCopy instanceof DateTimeImmutable); +var_dump($offsetImmutableCopy->format('Y-m-d H:i:s P T')); +var_dump($offsetImmutableCopy->getTimezone()->getName()); + +$abbrImmutable = new DateTimeImmutable('2023-10-27 10:00:00 EST'); + +var_dump(OPcache\volatile_store('abbr_immutable_datetime', $abbrImmutable)); + +$abbrImmutableCopy = OPcache\volatile_fetch('abbr_immutable_datetime'); +var_dump($abbrImmutableCopy instanceof DateTimeImmutable); +var_dump($abbrImmutableCopy->format('Y-m-d H:i:s P T')); +var_dump($abbrImmutableCopy->getTimezone()->getName()); + +class EventDateTime extends DateTime +{ + private string $label; + protected int $revision; + + public function __construct(string $time, DateTimeZone $timezone, string $label, int $revision) + { + parent::__construct($time, $timezone); + $this->label = $label; + $this->revision = $revision; + } + + public function describe(): string + { + return $this->label . ':' . $this->revision; + } +} + +$event = new EventDateTime('2026-06-15 09:30:00.123456', new DateTimeZone('Europe/Paris'), 'launch', 7); + +var_dump(OPcache\volatile_store('event_datetime', $event)); + +$eventCopy = OPcache\volatile_fetch('event_datetime'); +var_dump($eventCopy instanceof EventDateTime); +var_dump($eventCopy->format('Y-m-d H:i:s.u e')); +var_dump($eventCopy->describe()); + +class CustomSerializedDateTime extends DateTime +{ + public static int $serializeCount = 0; + public static int $unserializeCount = 0; + + private string $label; + + public function __construct(string $time, DateTimeZone $timezone, string $label) + { + parent::__construct($time, $timezone); + $this->label = $label; + } + + public function __serialize(): array + { + self::$serializeCount++; + + return parent::__serialize() + ['label' => $this->label]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCount++; + parent::__unserialize($data); + $this->label = $data['label']; + } + + public function label(): string + { + return $this->label; + } +} + +$custom = new CustomSerializedDateTime('2026-06-15 10:45:00.654321', new DateTimeZone('UTC'), 'fallback'); + +var_dump(OPcache\volatile_store('custom_datetime', $custom)); + +$customCopy = OPcache\volatile_fetch('custom_datetime'); +var_dump($customCopy instanceof CustomSerializedDateTime); +var_dump($customCopy->format('Y-m-d H:i:s.u e')); +var_dump($customCopy->label()); +var_dump(CustomSerializedDateTime::$serializeCount); +var_dump(CustomSerializedDateTime::$unserializeCount); + +class WakefulSerializedDateTime extends DateTime +{ + public static int $serializeCount = 0; + public static int $unserializeCount = 0; + + private string $label; + + public function __construct(string $time, DateTimeZone $timezone, string $label) + { + parent::__construct($time, $timezone); + $this->label = $label; + } + + public function __serialize(): array + { + self::$serializeCount++; + + return parent::__serialize() + ['label' => $this->label]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCount++; + parent::__unserialize($data); + $this->label = $data['label']; + } + + public function __sleep(): array + { + return ['label']; + } + + public function label(): string + { + return $this->label; + } +} + +$wakeful = new WakefulSerializedDateTime('2026-06-15 12:15:00.987654', new DateTimeZone('UTC'), 'fallback'); + +var_dump(OPcache\volatile_store('wakeful_datetime', $wakeful)); + +$wakefulCopy = OPcache\volatile_fetch('wakeful_datetime'); +var_dump($wakefulCopy instanceof WakefulSerializedDateTime); +var_dump($wakefulCopy->format('Y-m-d H:i:s.u e')); +var_dump($wakefulCopy->label()); +var_dump(WakefulSerializedDateTime::$serializeCount); +var_dump(WakefulSerializedDateTime::$unserializeCount); + +?> +--EXPECT-- +int(1) +int(1) +bool(true) +bool(true) +string(30) "2026-06-15 11:00:00.111111 UTC" +bool(true) +bool(true) +string(35) "2023-10-27 10:00:00 +05:30 GMT+0530" +string(6) "+05:30" +bool(true) +bool(true) +string(30) "2023-10-27 10:00:00 -05:00 EST" +string(3) "EST" +bool(true) +bool(true) +string(39) "2026-06-15 09:30:00.123456 Europe/Paris" +string(8) "launch:7" +bool(true) +bool(true) +string(30) "2026-06-15 10:45:00.654321 UTC" +string(8) "fallback" +int(0) +int(0) +bool(true) +bool(true) +string(30) "2026-06-15 12:15:00.987654 UTC" +string(8) "fallback" +int(1) +int(1) diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_002.phpt new file mode 100644 index 000000000000..65713eb13cf2 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_002.phpt @@ -0,0 +1,111 @@ +--TEST-- +OPcache __DirectCacheSafe covers DateTimeZone and DateInterval direct paths +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- +getAttributes(OPcache\__DirectCacheSafe::class))); +var_dump(count((new ReflectionClass(DateInterval::class))->getAttributes(OPcache\__DirectCacheSafe::class))); + +class TaggedTimeZone extends DateTimeZone +{ + private string $label; + + public function __construct(string $timezone, string $label) + { + parent::__construct($timezone); + $this->label = $label; + } + + public function label(): string + { + return $this->label; + } +} + +$timezone = new TaggedTimeZone('Europe/Paris', 'paris'); + +var_dump(OPcache\volatile_store('safe_direct_timezone', $timezone)); + +$timezoneCopy = OPcache\volatile_fetch('safe_direct_timezone'); +var_dump($timezoneCopy instanceof TaggedTimeZone); +var_dump($timezoneCopy->getName()); +var_dump($timezoneCopy->label()); + +$offsetTimezone = (new DateTimeImmutable('2023-10-27 10:00:00 +05:30'))->getTimezone(); + +var_dump(OPcache\volatile_store('safe_direct_offset_timezone', $offsetTimezone)); + +$offsetTimezoneCopy = OPcache\volatile_fetch('safe_direct_offset_timezone'); +var_dump($offsetTimezoneCopy instanceof DateTimeZone); +var_dump($offsetTimezoneCopy->getName()); + +$abbrTimezone = (new DateTimeImmutable('2023-10-27 10:00:00 EST'))->getTimezone(); + +var_dump(OPcache\volatile_store('safe_direct_abbr_timezone', $abbrTimezone)); + +$abbrTimezoneCopy = OPcache\volatile_fetch('safe_direct_abbr_timezone'); +var_dump($abbrTimezoneCopy instanceof DateTimeZone); +var_dump($abbrTimezoneCopy->getName()); + +class TaggedInterval extends DateInterval +{ + private string $label; + protected int $revision; + + public function __construct(string $duration, string $label, int $revision) + { + parent::__construct($duration); + $this->label = $label; + $this->revision = $revision; + } + + public function describe(): string + { + return $this->label . ':' . $this->revision; + } +} + +$interval = new TaggedInterval('P1Y2M3DT4H5M6S', 'window', 9); + +var_dump(OPcache\volatile_store('safe_direct_interval', $interval)); + +$intervalCopy = OPcache\volatile_fetch('safe_direct_interval'); +var_dump($intervalCopy instanceof TaggedInterval); +var_dump($intervalCopy->format('%y-%m-%d %h:%i:%s')); +var_dump($intervalCopy->describe()); + +$relativeInterval = DateInterval::createFromDateString('2 days 4 hours'); + +var_dump(OPcache\volatile_store('safe_direct_relative_interval', $relativeInterval)); + +$relativeIntervalCopy = OPcache\volatile_fetch('safe_direct_relative_interval'); +var_dump($relativeIntervalCopy instanceof DateInterval); +var_dump($relativeIntervalCopy->format('%d %h')); + +?> +--EXPECT-- +int(1) +int(1) +bool(true) +bool(true) +string(12) "Europe/Paris" +string(5) "paris" +bool(true) +bool(true) +string(6) "+05:30" +bool(true) +bool(true) +string(3) "EST" +bool(true) +bool(true) +string(11) "1-2-3 4:5:6" +string(8) "window:9" +bool(true) +bool(true) +string(3) "2 4" diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_003.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_003.phpt new file mode 100644 index 000000000000..58015f64cdcb --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_003.phpt @@ -0,0 +1,203 @@ +--TEST-- +OPcache __DirectCacheSafe covers SPL direct paths +--EXTENSIONS-- +opcache +spl +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- +getAttributes(OPcache\__DirectCacheSafe::class))); +var_dump(count((new ReflectionClass(ArrayObject::class))->getAttributes(OPcache\__DirectCacheSafe::class))); +var_dump(count((new ReflectionClass(ArrayIterator::class))->getAttributes(OPcache\__DirectCacheSafe::class))); +var_dump(count((new ReflectionClass(RecursiveArrayIterator::class))->getAttributes(OPcache\__DirectCacheSafe::class))); + +class TaggedFixedArray extends SplFixedArray +{ + private string $tag; + protected int $version; + + public function __construct(int $size, string $tag, int $version) + { + parent::__construct($size); + $this->tag = $tag; + $this->version = $version; + } + + public function describe(): string + { + return $this->tag . ':' . $this->version; + } +} + +$fixed = new TaggedFixedArray(3, 'vec', 7); +$fixed[0] = 'a'; +$fixed[1] = ['nested' => 1]; +$fixed[2] = 42; + +var_dump(OPcache\volatile_store('safe_direct_spl_fixed', $fixed)); + +$fixedCopy = OPcache\volatile_fetch('safe_direct_spl_fixed'); +var_dump($fixedCopy instanceof TaggedFixedArray); +var_dump($fixedCopy->getSize()); +var_dump($fixedCopy[0]); +var_dump($fixedCopy[1]['nested']); +var_dump($fixedCopy[2]); +var_dump($fixedCopy->describe()); + +class LabelIterator extends ArrayIterator +{ +} + +class TaggedCollection extends ArrayObject +{ + private string $type; + + public function __construct(array $data, string $type, string $iteratorClass) + { + parent::__construct($data, 0, $iteratorClass); + $this->type = $type; + } + + public function type(): string + { + return $this->type; + } +} + +$collection = new TaggedCollection(['alpha' => 10, 'beta' => 20], 'metric', LabelIterator::class); + +var_dump(OPcache\volatile_store('safe_direct_array_object', $collection)); + +$collectionCopy = OPcache\volatile_fetch('safe_direct_array_object'); +$collectionIterator = $collectionCopy->getIterator(); +var_dump($collectionCopy instanceof TaggedCollection); +var_dump($collectionIterator instanceof LabelIterator); +var_dump($collectionCopy['alpha']); +var_dump($collectionCopy['beta']); +var_dump($collectionCopy->type()); + +class TaggedIterator extends ArrayIterator +{ + private string $label; + + public function __construct(array $data, string $label) + { + parent::__construct($data); + $this->label = $label; + } + + public function label(): string + { + return $this->label; + } +} + +$iterator = new TaggedIterator([3, 5, 8], 'fib'); + +var_dump(OPcache\volatile_store('safe_direct_array_iterator', $iterator)); + +$iteratorCopy = OPcache\volatile_fetch('safe_direct_array_iterator'); +$iteratorCopy->rewind(); +var_dump($iteratorCopy instanceof TaggedIterator); +var_dump($iteratorCopy->count()); +var_dump($iteratorCopy->current()); +var_dump($iteratorCopy->label()); + +class TaggedRecursiveIterator extends RecursiveArrayIterator +{ + private string $name; + + public function __construct(array $data, string $name) + { + parent::__construct($data); + $this->name = $name; + } + + public function name(): string + { + return $this->name; + } +} + +$recursive = new TaggedRecursiveIterator(['leaf' => ['value' => 99]], 'tree'); + +var_dump(OPcache\volatile_store('safe_direct_recursive_array_iterator', $recursive)); + +$recursiveCopy = OPcache\volatile_fetch('safe_direct_recursive_array_iterator'); +$recursiveCopy->rewind(); +var_dump($recursiveCopy instanceof TaggedRecursiveIterator); +var_dump($recursiveCopy->count()); +var_dump($recursiveCopy->hasChildren()); +var_dump($recursiveCopy->name()); + +class CustomSerializedArrayObject extends ArrayObject +{ + public static int $serializeCalls = 0; + public static int $unserializeCalls = 0; + + public function __construct(array $data) + { + parent::__construct($data); + } + + public function __serialize(): array + { + self::$serializeCalls++; + return ['payload' => parent::__serialize()]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCalls++; + parent::__unserialize($data['payload']); + } +} + +$custom = new CustomSerializedArrayObject(['x' => 1]); + +var_dump(OPcache\volatile_store('safe_direct_array_override', $custom)); + +$customCopy = OPcache\volatile_fetch('safe_direct_array_override'); +var_dump($customCopy instanceof CustomSerializedArrayObject); +var_dump(CustomSerializedArrayObject::$serializeCalls); +var_dump(CustomSerializedArrayObject::$unserializeCalls); +var_dump($customCopy['x']); + +?> +--EXPECT-- +int(1) +int(1) +int(1) +int(1) +bool(true) +bool(true) +int(3) +string(1) "a" +int(1) +int(42) +string(5) "vec:7" +bool(true) +bool(true) +bool(true) +int(10) +int(20) +string(6) "metric" +bool(true) +bool(true) +int(3) +int(3) +string(3) "fib" +bool(true) +bool(true) +int(1) +bool(true) +string(4) "tree" +bool(true) +bool(true) +int(1) +int(1) +int(1) diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_004.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_004.phpt new file mode 100644 index 000000000000..3f61fac97ea7 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_004.phpt @@ -0,0 +1,101 @@ +--TEST-- +OPcache __DirectCacheSafe covers SPL edge branches +--EXTENSIONS-- +opcache +spl +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- +getSize()); +var_dump($plainFixedCopy[0]); +var_dump($plainFixedCopy[1]); + +$emptyFixed = new SplFixedArray(0); + +var_dump(OPcache\volatile_store('safe_direct_empty_fixed', $emptyFixed)); + +$emptyFixedCopy = OPcache\volatile_fetch('safe_direct_empty_fixed'); +var_dump($emptyFixedCopy instanceof SplFixedArray); +var_dump($emptyFixedCopy->getSize()); + +$flagged = new ArrayObject(['name' => 'val'], ArrayObject::ARRAY_AS_PROPS); + +var_dump(OPcache\volatile_store('safe_direct_array_props', $flagged)); + +$flaggedCopy = OPcache\volatile_fetch('safe_direct_array_props'); +var_dump($flaggedCopy instanceof ArrayObject); +var_dump($flaggedCopy->getFlags()); +var_dump($flaggedCopy->name); +var_dump($flaggedCopy['name']); +var_dump($flaggedCopy->getIteratorClass()); + +error_reporting(E_ALL & ~E_DEPRECATED); + +class SelfBackedCollection extends ArrayObject +{ + public int $slot = 0; +} + +$selfBacked = new SelfBackedCollection(['alpha' => 1]); +$selfBacked->exchangeArray($selfBacked); +$selfBacked['slot'] = 7; + +var_dump(OPcache\volatile_store('safe_direct_self_array_object', $selfBacked)); + +$selfBackedCopy = OPcache\volatile_fetch('safe_direct_self_array_object'); +var_dump($selfBackedCopy instanceof SelfBackedCollection); +var_dump($selfBackedCopy->slot); +var_dump($selfBackedCopy->count()); +var_dump(isset($selfBackedCopy['slot'])); +var_dump($selfBackedCopy->getIteratorClass()); + +$backedIterator = (new ArrayObject(['x' => 1]))->getIterator(); + +var_dump(OPcache\volatile_store('safe_direct_object_backed_iterator', $backedIterator)); + +$backedIteratorCopy = OPcache\volatile_fetch('safe_direct_object_backed_iterator'); +$backedIteratorCopy->rewind(); +var_dump($backedIteratorCopy instanceof ArrayIterator); +var_dump($backedIteratorCopy->count()); +var_dump($backedIteratorCopy->key()); +var_dump($backedIteratorCopy->current()); + +?> +--EXPECT-- +bool(true) +bool(true) +int(2) +string(5) "first" +string(6) "second" +bool(true) +bool(true) +int(0) +bool(true) +bool(true) +int(2) +string(3) "val" +string(3) "val" +string(13) "ArrayIterator" +bool(true) +bool(true) +int(7) +int(1) +bool(true) +string(13) "ArrayIterator" +bool(true) +bool(true) +int(1) +string(1) "x" +int(1) diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_fork_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_fork_001.phpt new file mode 100644 index 000000000000..82d08922509a --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_fork_001.phpt @@ -0,0 +1,147 @@ +--TEST-- +OPcache __DirectCacheSafe subclasses survive forked fetch and safe serializer overrides stay direct +--EXTENSIONS-- +opcache +pcntl +spl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- +label = $label; + $this->revision = $revision; + } + + public function describe(): string + { + return $this->label . ':' . $this->revision; + } +} + +class LabelIterator extends ArrayIterator +{ +} + +class TaggedCollection extends ArrayObject +{ + private string $type; + + public function __construct(array $data, string $type, string $iteratorClass) + { + parent::__construct($data, 0, $iteratorClass); + $this->type = $type; + } + + public function type(): string + { + return $this->type; + } +} + +class CustomSerializedDateTime extends DateTime +{ + public static int $unserializeCount = 0; + + private string $label; + + public function __construct(string $time, DateTimeZone $timezone, string $label) + { + parent::__construct($time, $timezone); + $this->label = $label; + } + + public function __serialize(): array + { + return parent::__serialize() + ['label' => $this->label]; + } + + public function __unserialize(array $data): void + { + self::$unserializeCount++; + parent::__unserialize($data); + $this->label = $data['label']; + } + + public function label(): string + { + return $this->label; + } +} + +$readyFile = sys_get_temp_dir() . '/opcache_volatile_cache_direct_cache_safe_fork_' . getmypid() . '.ready'; +@unlink($readyFile); + +$pid = pcntl_fork(); +if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); +} + +if ($pid === 0) { + $deadline = microtime(true) + 5.0; + + while (!file_exists($readyFile)) { + if (microtime(true) >= $deadline) { + fwrite(STDERR, "parent did not signal readiness\n"); + exit(1); + } + + usleep(1000); + } + + $event = OPcache\volatile_fetch('safe_direct_event_datetime'); + $collection = OPcache\volatile_fetch('safe_direct_tagged_collection'); + $fallback = OPcache\volatile_fetch('safe_direct_custom_datetime'); + $iterator = $collection->getIterator(); + + echo $event->format('Y-m-d H:i:s.u e'), ',', $event->describe(), "\n"; + echo $collection->type(), ',', ($iterator instanceof LabelIterator ? 'LabelIterator' : get_debug_type($iterator)), ',', $collection['alpha'], ',', $collection['beta'], "\n"; + echo $fallback->format('Y-m-d H:i:s.u e'), ',', $fallback->label(), ',', CustomSerializedDateTime::$unserializeCount, "\n"; + exit(0); +} + +$event = new EventDateTime('2026-06-15 09:30:00.123456', new DateTimeZone('Europe/Paris'), 'launch', 7); +$collection = new TaggedCollection(['alpha' => 10, 'beta' => 20], 'metric', LabelIterator::class); +$fallback = new CustomSerializedDateTime('2026-06-15 10:45:00.654321', new DateTimeZone('UTC'), 'fallback'); + +if (!OPcache\volatile_store('safe_direct_event_datetime', $event)) { + throw new RuntimeException('Failed to store safe_direct_event_datetime'); +} +if (!OPcache\volatile_store('safe_direct_tagged_collection', $collection)) { + throw new RuntimeException('Failed to store safe_direct_tagged_collection'); +} +if (!OPcache\volatile_store('safe_direct_custom_datetime', $fallback)) { + throw new RuntimeException('Failed to store safe_direct_custom_datetime'); +} + +file_put_contents($readyFile, 'ready'); +pcntl_waitpid($pid, $status); +@unlink($readyFile); + +?> +--EXPECT-- +2026-06-15 09:30:00.123456 Europe/Paris,launch:7 +metric,LabelIterator,10,20 +2026-06-15 10:45:00.654321 UTC,fallback,0 +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt new file mode 100644 index 000000000000..9c95f9f77954 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt @@ -0,0 +1,55 @@ +--TEST-- +OPcache __DirectCacheSafe rejects unstorable values in SPL subclass state +--EXTENSIONS-- +opcache +spl +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- +getMessage(), "\n"; + } +} + +$resource = fopen(__FILE__, 'r'); +$fixed_array = new SafeDirectUnstorableFixedArray(1); +$fixed_array[0] = $resource; +$array_object = new SafeDirectUnstorableArrayObject(['value' => static fn () => true]); + +var_dump(OPcache\volatile_store('fixed-resource', $fixed_array)); +var_dump(OPcache\volatile_fetch('fixed-resource', 'missing')); +var_dump(OPcache\volatile_store('array-object-closure', $array_object)); +var_dump(OPcache\volatile_fetch('array-object-closure', 'missing')); + +dump_persistent_exception(static fn () => OPcache\persistent_store('fixed-resource', $fixed_array)); +dump_persistent_exception(static fn () => OPcache\persistent_store('array-object-closure', $array_object)); + +fclose($resource); + +?> +--EXPECT-- +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +resources and Closure objects cannot be stored in the static cache +resources and Closure objects cannot be stored in the static cache diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc new file mode 100644 index 000000000000..2cc073a96833 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc @@ -0,0 +1,6 @@ +isInternal()); +var_dump($marker->isFinal()); + +var_dump(count((new ReflectionClass(DateTimeImmutable::class))->getAttributes(OPcache\__DirectCacheSafe::class))); + +$value = new DateTimeImmutable('2026-01-02 03:04:05', new DateTimeZone('UTC')); +var_dump(OPcache\volatile_store('hidden_safe_direct_datetime', $value)); +var_dump(OPcache\volatile_fetch('hidden_safe_direct_datetime')->format('Y-m-d H:i:s e')); + +require __DIR__ . '/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc'; + +?> +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) +int(1) +bool(true) +string(23) "2026-01-02 03:04:05 UTC" + +Fatal error: Only internal classes can be marked with #[OPcache\__DirectCacheSafe] in %sstatic_cache_volatile_cache_direct_cache_safe_visibility_001.inc on line %d diff --git a/ext/opcache/tests/static_cache_volatile_cache_expunge_before_relocation_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_expunge_before_relocation_001.phpt new file mode 100644 index 000000000000..849466fcf66b --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_expunge_before_relocation_001.phpt @@ -0,0 +1,48 @@ +--TEST-- +OPcache volatile cache expunges expired entries before relocating fragmented payload blocks +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +--FILE-- +name); +var_dump(OPcache\volatile_fetch('expired-tail', 'expired')); +var_dump(strlen(OPcache\volatile_fetch('after-expired'))); +var_dump(strlen(OPcache\volatile_fetch('first'))); +var_dump(strlen(OPcache\volatile_fetch('third'))); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +string(5) "probe" +string(7) "expired" +int(2400000) +int(1200000) +int(1200000) diff --git a/ext/opcache/tests/static_cache_volatile_cache_fetch_exists_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_fetch_exists_001.phpt new file mode 100644 index 000000000000..b78e88e942d5 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_fetch_exists_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +OPcache volatile_fetch default value and volatile_exists +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + +--EXPECT-- +bool(false) +NULL +string(8) "fallback" +bool(true) +NULL +bool(true) +bool(true) +bool(false) +bool(true) +bool(false) diff --git a/ext/opcache/tests/static_cache_volatile_cache_info_surface_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_info_surface_001.phpt new file mode 100644 index 000000000000..961ea89fc0b4 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_info_surface_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +OPcache volatile cache status and info surface +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + 0); +var_dump(is_string($status['volatile_cache']['shared_model'])); +var_dump($info == $status['volatile_cache']); +var_dump(array_key_exists('failure_reason', $info)); + +?> +--EXPECTF-- +int(33554432) +bool(true) +bool(true) +bool(false) +bool(true) +int(33554432) +int(33554432) +bool(true) +bool(true) +bool(true) +bool(false) diff --git a/ext/opcache/tests/static_cache_volatile_cache_lookup_cache_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_lookup_cache_001.phpt new file mode 100644 index 000000000000..7b22a7382de5 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_lookup_cache_001.phpt @@ -0,0 +1,37 @@ +--TEST-- +OPcache volatile cache request-local lookup cache invalidates on writes +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + +--EXPECT-- +string(4) "MISS" +bool(true) +string(5) "value" +bool(true) +string(4) "gone" +string(4) "MISS" +bool(true) +string(8) "clear me" +string(4) "MISS" diff --git a/ext/opcache/tests/static_cache_volatile_cache_lookup_cache_fork_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_lookup_cache_fork_001.phpt new file mode 100644 index 000000000000..b0e0b08600fa --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_lookup_cache_fork_001.phpt @@ -0,0 +1,117 @@ +--TEST-- +OPcache volatile cache request-local lookup cache sees cross-process updates on next fetch +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- += $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +function cleanup_files(string ...$paths): void +{ + foreach ($paths as $path) { + @unlink($path); + } +} + +function run_lookup_cache_scenario(string $key, ?string $initialValue, string $action, ?string $updatedValue): void +{ + $prefix = sys_get_temp_dir() . '/opcache_lookup_cache_' . $key . '_' . getmypid(); + $readyFile = $prefix . '.ready'; + $doneFile = $prefix . '.done'; + $resultFile = $prefix . '.result'; + + cleanup_files($readyFile, $doneFile, $resultFile); + + if ($initialValue === null) { + OPcache\volatile_delete($key); + } else { + OPcache\volatile_store($key, $initialValue); + } + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + $first = fetch_or_miss($key); + file_put_contents($readyFile, 'ready'); + wait_for_file($doneFile); + $second = fetch_or_miss($key); + file_put_contents($resultFile, $first . "\n" . $second); + exit(0); + } + + wait_for_file($readyFile); + switch ($action) { + case 'store': + OPcache\volatile_store($key, $updatedValue); + break; + case 'delete': + OPcache\volatile_delete($key); + break; + case 'clear': + OPcache\volatile_clear(); + break; + default: + throw new RuntimeException("unknown action {$action}"); + } + file_put_contents($doneFile, 'done'); + pcntl_waitpid($pid, $status); + + echo trim((string) file_get_contents($resultFile)), "\n"; + cleanup_files($readyFile, $doneFile, $resultFile); +} + +OPcache\volatile_clear(); + +run_lookup_cache_scenario('lookup_hit_store_key', 'old', 'store', 'new'); +run_lookup_cache_scenario('lookup_miss_store_key', null, 'store', 'created'); +run_lookup_cache_scenario('lookup_hit_delete_key', 'old', 'delete', null); +run_lookup_cache_scenario('lookup_hit_clear_key', 'old', 'clear', null); + +?> +--EXPECT-- +old +new +MISS +created +old +MISS +old +MISS +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_volatile_cache_materialization_clear_fork_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_materialization_clear_fork_001.phpt new file mode 100644 index 000000000000..394c0a7ca9c7 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_materialization_clear_fork_001.phpt @@ -0,0 +1,105 @@ +--TEST-- +OPcache volatile cache fetched shared graph survives cross-process volatile_clear() +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $i, + 'text' => str_repeat(chr(65 + ($i % 26)), 64), + 'nested' => ['value' => $i * 3], + ]; + } + + return $rows; +} + +function wait_for_file(string $path): void +{ + $deadline = microtime(true) + 5.0; + + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +$key = 'materialized_clear_payload'; +$prefix = sys_get_temp_dir() . '/opcache_volatile_cache_materialized_clear_' . getmypid(); +$readyFile = $prefix . '.ready'; +$doneFile = $prefix . '.done'; +$resultFile = $prefix . '.result'; +@unlink($readyFile); +@unlink($doneFile); +@unlink($resultFile); + +OPcache\volatile_clear(); +if (!OPcache\volatile_store($key, new ClearPayload(build_rows()))) { + throw new RuntimeException('store failed'); +} + +$pid = pcntl_fork(); +if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); +} + +if ($pid === 0) { + $fetched = OPcache\volatile_fetch($key); + $before = $fetched->rows[123]['text']; + file_put_contents($readyFile, 'ready'); + wait_for_file($doneFile); + $fetched->rows[123]['text'] = 'changed after clear'; + $after = $fetched->rows[123]['text']; + + $refetch = OPcache\volatile_fetch($key, 'MISS') === 'MISS' ? 'MISS' : 'HIT'; + + file_put_contents($resultFile, $before . "\n" . $after . "\n" . $refetch); + exit(0); +} + +wait_for_file($readyFile); +OPcache\volatile_clear(); +file_put_contents($doneFile, 'done'); +pcntl_waitpid($pid, $status); + +echo file_get_contents($resultFile), "\n"; +@unlink($readyFile); +@unlink($doneFile); +@unlink($resultFile); + +?> +--EXPECT-- +TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT +changed after clear +MISS +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_volatile_cache_materialization_clear_reuse_fork_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_materialization_clear_reuse_fork_001.phpt new file mode 100644 index 000000000000..f7ecdce8a696 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_materialization_clear_reuse_fork_001.phpt @@ -0,0 +1,112 @@ +--TEST-- +OPcache volatile cache fetched shared graph survives cross-process clear and reuse +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $i, + 'text' => str_repeat($prefix, 64), + 'nested' => ['value' => $i * $multiplier], + ]; + } + + return $rows; +} + +function wait_for_file(string $path): void +{ + $deadline = microtime(true) + 5.0; + + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +$key = 'materialized_clear_reuse_payload'; +$overwriteKey = 'materialized_clear_reuse_overwrite'; +$prefix = sys_get_temp_dir() . '/opcache_volatile_cache_materialized_clear_reuse_' . getmypid(); +$readyFile = $prefix . '.ready'; +$doneFile = $prefix . '.done'; +$resultFile = $prefix . '.result'; +@unlink($readyFile); +@unlink($doneFile); +@unlink($resultFile); + +OPcache\volatile_clear(); +if (!OPcache\volatile_store($key, new ClearReusePayload(build_rows('T', 3)))) { + throw new RuntimeException('store failed'); +} + +$pid = pcntl_fork(); +if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); +} + +if ($pid === 0) { + $fetched = OPcache\volatile_fetch($key); + file_put_contents($readyFile, 'ready'); + wait_for_file($doneFile); + + $before = $fetched->rows[123]['text']; + $fetched->rows[123]['text'] = 'changed after clear and reuse'; + $after = $fetched->rows[123]['text']; + $nested = $fetched->rows[123]['nested']['value']; + + $refetch = OPcache\volatile_fetch($key, 'MISS') === 'MISS' ? 'MISS' : 'HIT'; + + file_put_contents($resultFile, $before . "\n" . $after . "\n" . $nested . "\n" . $refetch); + exit(0); +} + +wait_for_file($readyFile); +OPcache\volatile_clear(); +if (!OPcache\volatile_store($overwriteKey, new ClearReusePayload(build_rows('X', 7)))) { + throw new RuntimeException('overwrite store failed'); +} +file_put_contents($doneFile, 'done'); +pcntl_waitpid($pid, $status); + +echo file_get_contents($resultFile), "\n"; +@unlink($readyFile); +@unlink($doneFile); +@unlink($resultFile); + +?> +--EXPECT-- +TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT +changed after clear and reuse +369 +MISS +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_001.phpt new file mode 100644 index 000000000000..6ed87af4ee69 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_001.phpt @@ -0,0 +1,101 @@ +--TEST-- +OPcache volatile cache should isolate post-mutation object fetches across processes +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $i, + 'text' => str_repeat(chr(65 + ($i % 26)), 96), + 'flags' => [$i, $i + 1, $i + 2, $i + 3], + ]; + } + + return $rows; +} + +$readyFile = sys_get_temp_dir() . '/opcache_volatile_cache_materialization_plain_' . getmypid() . '.ready'; +@unlink($readyFile); + +$pid = pcntl_fork(); +if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); +} + +if ($pid === 0) { + $deadline = microtime(true) + 5.0; + $expectedText = str_repeat(chr(65 + (100 % 26)), 96); + $canMeasureRequestAllocations = getenv('USE_ZEND_ALLOC') !== '0'; + + while (!file_exists($readyFile)) { + if (microtime(true) >= $deadline) { + fwrite(STDERR, "parent did not signal readiness\n"); + exit(1); + } + + usleep(1000); + } + + $before = memory_get_usage(); + $fetched = OPcache\volatile_fetch('materialization_plain_payload'); + $readOnly = $fetched->rows[100]['text']; + $afterFetch = memory_get_usage(); + $fetched->rows[100]['text'] = 'changed'; + $afterMutate = memory_get_usage(); + $secondFetch = OPcache\volatile_fetch('materialization_plain_payload'); + + var_dump($readOnly === $expectedText + && (!$canMeasureRequestAllocations || ($afterFetch - $before) < 262144)); + var_dump($fetched->rows[100]['text'] === 'changed' + && $secondFetch->rows[100]['text'] === $expectedText + && (!$canMeasureRequestAllocations || ($afterMutate - $afterFetch) > 131072)); + exit(0); +} + +if (!OPcache\volatile_store('materialization_plain_payload', new LargePayload(build_rows(), 'plain'))) { + throw new RuntimeException('Failed to store materialization_plain_payload'); +} + +file_put_contents($readyFile, 'ready'); +pcntl_waitpid($pid, $status); +@unlink($readyFile); + +$fetched = OPcache\volatile_fetch('materialization_plain_payload'); +var_dump($fetched->rows[100]['text'] === str_repeat(chr(65 + (100 % 26)), 96)); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_002.phpt new file mode 100644 index 000000000000..05d1c3df1e64 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_002.phpt @@ -0,0 +1,115 @@ +--TEST-- +OPcache volatile cache should isolate nested post-mutation fetches even with __DirectCacheSafe properties +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $i, + 'text' => str_repeat(chr(65 + ($i % 26)), 96), + 'flags' => [$i, $i + 1, $i + 2, $i + 3], + ]; + } + + return $rows; +} + +$readyFile = sys_get_temp_dir() . '/opcache_volatile_cache_materialization_nested_' . getmypid() . '.ready'; +@unlink($readyFile); + +$pid = pcntl_fork(); +if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); +} + +if ($pid === 0) { + $deadline = microtime(true) + 5.0; + $expectedText = str_repeat(chr(65 + (100 % 26)), 96); + $expectedPrefix = '2026-06-15T09:30:00+00:00|'; + $canMeasureRequestAllocations = getenv('USE_ZEND_ALLOC') !== '0'; + + while (!file_exists($readyFile)) { + if (microtime(true) >= $deadline) { + fwrite(STDERR, "parent did not signal readiness\n"); + exit(1); + } + + usleep(1000); + } + + $before = memory_get_usage(); + $fetched = OPcache\volatile_fetch('materialization_nested_payload'); + $readOnly = $fetched->timestamp->format(DateTimeInterface::ATOM) . '|' . $fetched->payload->rows[100]['text']; + $afterFetch = memory_get_usage(); + $fetched->payload->rows[100]['text'] = 'changed'; + $afterMutate = memory_get_usage(); + $secondFetch = OPcache\volatile_fetch('materialization_nested_payload'); + + var_dump($readOnly === $expectedPrefix . $expectedText + && (!$canMeasureRequestAllocations || ($afterFetch - $before) < 262144)); + var_dump($fetched->payload->rows[100]['text'] === 'changed' + && $secondFetch->payload->rows[100]['text'] === $expectedText + && (!$canMeasureRequestAllocations || ($afterMutate - $afterFetch) > 131072)); + exit(0); +} + +$payload = new WrappedPayload( + new LargePayload(build_rows(), 'nested'), + new DateTimeImmutable('2026-06-15 09:30:00', new DateTimeZone('UTC')), +); + +if (!OPcache\volatile_store('materialization_nested_payload', $payload)) { + throw new RuntimeException('Failed to store materialization_nested_payload'); +} + +file_put_contents($readyFile, 'ready'); +pcntl_waitpid($pid, $status); +@unlink($readyFile); + +$fetched = OPcache\volatile_fetch('materialization_nested_payload'); +var_dump($fetched->payload->rows[100]['text'] === str_repeat(chr(65 + (100 % 26)), 96)); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_volatile_cache_materialization_overwrite_reuse_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_materialization_overwrite_reuse_001.phpt new file mode 100644 index 000000000000..4e221ab6384b --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_materialization_overwrite_reuse_001.phpt @@ -0,0 +1,74 @@ +--TEST-- +OPcache volatile cache fetched shared graph survives same-request overwrite and reuse +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $i, + 'text' => str_repeat($prefix, 64), + 'nested' => ['value' => $i * $multiplier], + ]; + } + + return $rows; +} + +function build_payload(string $prefix, int $multiplier): array +{ + return [ + 'name' => 'payload-' . $prefix, + 'rows' => build_rows($prefix, $multiplier), + ]; +} + +$key = 'materialized_overwrite_payload'; +$reuseKey = 'materialized_overwrite_reuse_payload'; + +OPcache\volatile_clear(); +if (!OPcache\volatile_store($key, build_payload('A', 3))) { + throw new RuntimeException('initial store failed'); +} + +$fetched = OPcache\volatile_fetch($key); +$before = $fetched['rows'][123]['text']; + +if (!OPcache\volatile_store($key, build_payload('B', 7))) { + throw new RuntimeException('overwrite store failed'); +} +if (!OPcache\volatile_store($reuseKey, build_payload('C', 11))) { + throw new RuntimeException('reuse store failed'); +} + +$after = $fetched['rows'][123]['text']; +$nested = $fetched['rows'][123]['nested']['value']; +$refetched = OPcache\volatile_fetch($key); +$reused = OPcache\volatile_fetch($reuseKey); + +echo $before, "\n"; +echo $after, "\n"; +echo $nested, "\n"; +echo $refetched['rows'][123]['text'], "\n"; +echo $refetched['rows'][123]['nested']['value'], "\n"; +echo $reused['rows'][123]['text'], "\n"; +echo $reused['rows'][123]['nested']['value'], "\n"; + +?> +--EXPECT-- +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +369 +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +861 +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +1353 \ No newline at end of file diff --git a/ext/opcache/tests/static_cache_volatile_cache_no_atomic_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_no_atomic_001.phpt new file mode 100644 index 000000000000..686cdce68681 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_no_atomic_001.phpt @@ -0,0 +1,22 @@ +--TEST-- +OPcache volatile cache does not expose atomic public API +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + +--EXPECT-- +bool(false) +bool(false) +bool(true) +bool(true) diff --git a/ext/opcache/tests/static_cache_volatile_cache_overflow_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_overflow_001.phpt new file mode 100644 index 000000000000..59dc36415a4a --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_overflow_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +OPcache volatile cache expunges entries under memory pressure without wiping oversized writes +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +int(3000000) +bool(true) +bool(false) +string(8) "survives" diff --git a/ext/opcache/tests/static_cache_volatile_cache_process_lock_mode_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_process_lock_mode_001.phpt new file mode 100644 index 000000000000..a6f349e0378e --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_process_lock_mode_001.phpt @@ -0,0 +1,244 @@ +--TEST-- +OPcache volatile cache process lock blocks writers behind active readers +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- + MAX_COMMAND_OUTPUT_BYTES) { + return substr($contents, 0, MAX_COMMAND_OUTPUT_BYTES) . "\n...[output truncated]\n"; + } + + return $contents; +} + +function runCommand(array $command): array +{ + $stdoutFile = tempnam(sys_get_temp_dir(), 'opcache-process-lock-out-'); + $stderrFile = tempnam(sys_get_temp_dir(), 'opcache-process-lock-err-'); + if ($stdoutFile === false || $stderrFile === false) { + throw new RuntimeException('failed to create temporary files'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['file', $stdoutFile, 'w'], + 2 => ['file', $stderrFile, 'w'], + ]; + + $process = proc_open($command, $descriptorSpec, $pipes); + if (!is_resource($process)) { + @unlink($stdoutFile); + @unlink($stderrFile); + throw new RuntimeException('proc_open failed'); + } + + fclose($pipes[0]); + $status = proc_close($process); + + $stdout = readCommandOutput($stdoutFile); + $stderr = readCommandOutput($stderrFile); + @unlink($stdoutFile); + @unlink($stderrFile); + + return [$status, $stdout, $stderr]; +} + +function parseBuildFlags(string $flags): array +{ + $tokens = preg_split('/\s+/', trim($flags)); + if ($tokens === false) { + return []; + } + + return array_values(array_filter($tokens, static function (string $token): bool { + return $token !== '' && !str_contains($token, '$('); + })); +} + +function resolveBuildFlags(string $buildRoot, array $variables): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + $flags = []; + + if ($makefile === false) { + return $flags; + } + + foreach ($variables as $variable) { + if (preg_match('/^' . preg_quote($variable, '/') . '\s*=\s*(.+)$/m', $makefile, $matches)) { + $flags = array_merge($flags, parseBuildFlags($matches[1])); + } + } + + return $flags; +} + +function resolveBuildCommand(string $buildRoot, string $variable, array $fallback): array +{ + $value = getenv($variable); + if (is_string($value) && $value !== '') { + $tokens = parseBuildFlags($value); + if ($tokens !== []) { + return $tokens; + } + } + + $tokens = resolveBuildFlags($buildRoot, [$variable]); + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveBuildRoot(string $root): string +{ + $buildRoot = null; + $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; + if ($php) { + $php = realpath($php); + if ($php !== false) { + $candidate = dirname($php, 3); + if (file_exists($candidate . '/.libs/libphp.a') || file_exists($candidate . '/libs/libphp.a')) { + $buildRoot = $candidate; + } + } + } + + if ($buildRoot === null && (file_exists($root . '/.libs/libphp.a') || file_exists($root . '/libs/libphp.a'))) { + $buildRoot = $root; + } + + if ($buildRoot === null) { + throw new RuntimeException('Failed to resolve build root'); + } + + return $buildRoot; +} + +function resolveExtraLibs(string $buildRoot): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + if ($makefile !== false && preg_match('/^EXTRA_LIBS\s*=\s*(.+)$/m', $makefile, $matches)) { + $libs = preg_split('/\s+/', trim($matches[1])); + if ($libs !== false && $libs !== ['']) { + return $libs; + } + } + + return [ + '-lm', + '-lpthread', + '-ldl', + '-lresolv', + '-lutil', + ]; +} + +$root = realpath(__DIR__ . '/../../..'); +if ($root === false) { + throw new RuntimeException('Failed to resolve source root'); +} + +$buildRoot = resolveBuildRoot($root); +$source = __DIR__ . '/helpers/volatile_cache_process_lock_mode_001.c'; +$binary = __DIR__ . '/helpers/volatile_cache_process_lock_mode_001.out'; +$compiler = resolveBuildCommand($buildRoot, 'CC', ['gcc']); +$buildFlags = resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$extraLibs = resolveExtraLibs($buildRoot); + +$compileCommand = [ + ...$compiler, + ...$buildFlags, + '-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '-DHAVE_CONFIG_H', + '-I' . $buildRoot, + '-I' . $buildRoot . '/main', + '-I' . $buildRoot . '/Zend', + '-I' . $buildRoot . '/TSRM', + '-I' . $buildRoot . '/sapi/embed', + '-I' . $buildRoot . '/ext/opcache', + '-I' . $buildRoot . '/ext/date/lib', + '-I' . $root, + '-I' . $root . '/main', + '-I' . $root . '/Zend', + '-I' . $root . '/TSRM', + '-I' . $root . '/sapi/embed', + '-I' . $root . '/ext/opcache', + '-o', + $binary, + $source, + $root . '/sapi/embed/php_embed.c', + file_exists($buildRoot . '/.libs/libphp.a') ? $buildRoot . '/.libs/libphp.a' : $buildRoot . '/libs/libphp.a', +]; +$compileCommand = array_merge($compileCommand, $extraLibs); + +[$status, $stdout, $stderr] = runCommand($compileCommand); +if ($status !== 0) { + echo $stdout, $stderr; + exit(1); +} + +@chmod($binary, 0755); +[$status, $stdout, $stderr] = runCommand([$binary]); +echo $stdout; +if ($status !== 0) { + echo $stderr; + exit(1); +} + +?> +--CLEAN-- + +--EXPECT-- +ok diff --git a/ext/opcache/tests/static_cache_volatile_cache_retired_shared_graph_free_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_retired_shared_graph_free_001.phpt new file mode 100644 index 000000000000..3c54e5e19527 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_retired_shared_graph_free_001.phpt @@ -0,0 +1,240 @@ +--TEST-- +OPcache volatile cache frees retired shared graph payloads after releasing the read lock +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- + MAX_COMMAND_OUTPUT_BYTES) { + return substr($contents, 0, MAX_COMMAND_OUTPUT_BYTES) . "\n...[output truncated]\n"; + } + + return $contents; +} + +function runCommand(array $command): array +{ + $stdoutFile = tempnam(sys_get_temp_dir(), 'opcache-retired-graph-out-'); + $stderrFile = tempnam(sys_get_temp_dir(), 'opcache-retired-graph-err-'); + if ($stdoutFile === false || $stderrFile === false) { + throw new RuntimeException('failed to create temporary files'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['file', $stdoutFile, 'w'], + 2 => ['file', $stderrFile, 'w'], + ]; + + $process = proc_open($command, $descriptorSpec, $pipes); + if (!is_resource($process)) { + @unlink($stdoutFile); + @unlink($stderrFile); + throw new RuntimeException('proc_open failed'); + } + + fclose($pipes[0]); + $status = proc_close($process); + + $stdout = readCommandOutput($stdoutFile); + $stderr = readCommandOutput($stderrFile); + @unlink($stdoutFile); + @unlink($stderrFile); + + return [$status, $stdout, $stderr]; +} + +function parseBuildFlags(string $flags): array +{ + $tokens = preg_split('/\s+/', trim($flags)); + if ($tokens === false) { + return []; + } + + return array_values(array_filter($tokens, static function (string $token): bool { + return $token !== '' && !str_contains($token, '$('); + })); +} + +function resolveBuildFlags(string $buildRoot, array $variables): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + $flags = []; + + if ($makefile === false) { + return $flags; + } + + foreach ($variables as $variable) { + if (preg_match('/^' . preg_quote($variable, '/') . '\s*=\s*(.+)$/m', $makefile, $matches)) { + $flags = array_merge($flags, parseBuildFlags($matches[1])); + } + } + + return $flags; +} + +function resolveBuildCommand(string $buildRoot, string $variable, array $fallback): array +{ + $value = getenv($variable); + if (is_string($value) && $value !== '') { + $tokens = parseBuildFlags($value); + if ($tokens !== []) { + return $tokens; + } + } + + $tokens = resolveBuildFlags($buildRoot, [$variable]); + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveBuildRoot(string $root): string +{ + $buildRoot = null; + $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; + if ($php) { + $php = realpath($php); + if ($php !== false) { + $candidate = dirname($php, 3); + if (file_exists($candidate . '/.libs/libphp.a') || file_exists($candidate . '/libs/libphp.a')) { + $buildRoot = $candidate; + } + } + } + + if ($buildRoot === null && (file_exists($root . '/.libs/libphp.a') || file_exists($root . '/libs/libphp.a'))) { + $buildRoot = $root; + } + + if ($buildRoot === null) { + throw new RuntimeException('Failed to resolve build root'); + } + + return $buildRoot; +} + +function resolveExtraLibs(string $buildRoot): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + if ($makefile !== false && preg_match('/^EXTRA_LIBS\s*=\s*(.+)$/m', $makefile, $matches)) { + $libs = preg_split('/\s+/', trim($matches[1])); + if ($libs !== false && $libs !== ['']) { + return $libs; + } + } + + return [ + '-lm', + '-lpthread', + '-ldl', + '-lresolv', + '-lutil', + ]; +} + +$root = realpath(__DIR__ . '/../../..'); +if ($root === false) { + throw new RuntimeException('Failed to resolve source root'); +} + +$buildRoot = resolveBuildRoot($root); +$source = __DIR__ . '/helpers/volatile_cache_retired_shared_graph_free_001.c'; +$binary = __DIR__ . '/helpers/volatile_cache_retired_shared_graph_free_001.out'; +$compiler = resolveBuildCommand($buildRoot, 'CC', ['gcc']); +$buildFlags = resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$extraLibs = resolveExtraLibs($buildRoot); + +$compileCommand = [ + ...$compiler, + ...$buildFlags, + '-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '-DHAVE_CONFIG_H', + '-I' . $buildRoot, + '-I' . $buildRoot . '/main', + '-I' . $buildRoot . '/Zend', + '-I' . $buildRoot . '/TSRM', + '-I' . $buildRoot . '/sapi/embed', + '-I' . $buildRoot . '/ext/opcache', + '-I' . $buildRoot . '/ext/date/lib', + '-I' . $root, + '-I' . $root . '/main', + '-I' . $root . '/Zend', + '-I' . $root . '/TSRM', + '-I' . $root . '/sapi/embed', + '-I' . $root . '/ext/opcache', + '-o', + $binary, + $source, + $root . '/sapi/embed/php_embed.c', + file_exists($buildRoot . '/.libs/libphp.a') ? $buildRoot . '/.libs/libphp.a' : $buildRoot . '/libs/libphp.a', +]; +$compileCommand = array_merge($compileCommand, $extraLibs); + +[$status, $stdout, $stderr] = runCommand($compileCommand); +if ($status !== 0) { + echo $stdout, $stderr; + exit(1); +} + +@chmod($binary, 0755); +[$status, $stdout, $stderr] = runCommand([$binary]); +echo $stdout; +if ($status !== 0) { + echo $stderr; + exit(1); +} + +?> +--CLEAN-- + +--EXPECT-- +ok diff --git a/ext/opcache/tests/static_cache_volatile_cache_shared_graph_dynamic_array_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_shared_graph_dynamic_array_001.phpt new file mode 100644 index 000000000000..c0ab084f246a --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_shared_graph_dynamic_array_001.phpt @@ -0,0 +1,62 @@ +--TEST-- +OPcache volatile cache shared graph supports packed arrays with object elements +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $index, 'bucket' => $index % 4]); + } + + return $items; +} + +$items = build_packed_graph_items(); +$payload = new PackedGraphPayload($items); + +var_dump(OPcache\volatile_store('packed_graph_payload', $payload)); +var_dump(OPcache\volatile_store('packed_graph_root', $items)); + +$payloadCopy = OPcache\volatile_fetch('packed_graph_payload'); +$rootCopy = OPcache\volatile_fetch('packed_graph_root'); + +var_dump($payloadCopy instanceof PackedGraphPayload); +var_dump($payloadCopy->items[7] instanceof PackedGraphItem); +var_dump($payloadCopy->items[7]->name); +var_dump($payloadCopy->items[7]->metadata['bucket']); +var_dump($rootCopy[12] instanceof PackedGraphItem); +var_dump($rootCopy[12]->name); +var_dump($rootCopy[12]->metadata['index']); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +string(6) "item-7" +int(3) +bool(true) +string(7) "item-12" +int(12) diff --git a/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt new file mode 100644 index 000000000000..171873cfd6f8 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt @@ -0,0 +1,155 @@ +--TEST-- +OPcache static cache rejects resource and Closure values +--EXTENSIONS-- +opcache +spl +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + true; +$resource_object = new StaticCacheUnsupportedValueBox($resource); +$closure_object = new StaticCacheUnsupportedValueBox($closure); +$resource_fixed_array = new SplFixedArray(1); +$resource_fixed_array[0] = $resource; +$closure_fixed_array = new SplFixedArray(1); +$closure_fixed_array[0] = $closure; +$resource_array_object = new ArrayObject(['value' => $resource]); +$closure_array_object = new ArrayObject(['value' => $closure]); + +class StaticCacheUnsupportedSerializedPayload +{ + public static mixed $value = null; + + public function __serialize(): array + { + return ['value' => self::$value]; + } +} + +$serialized_payload = new StaticCacheUnsupportedSerializedPayload(); + +function dump_type_error(Closure $callback): void +{ + try { + $callback(); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } +} + +function dump_static_cache_exception(Closure $callback): void +{ + try { + $callback(); + } catch (StaticCacheException $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; + } +} + +dump_type_error(static fn () => OPcache\volatile_store('resource', $resource)); +var_dump(OPcache\volatile_fetch('resource', 'missing')); +dump_type_error(static fn () => OPcache\volatile_store('closure', $closure)); +var_dump(OPcache\volatile_fetch('closure', 'missing')); + +var_dump(OPcache\volatile_store_array(['nested-resource' => ['value' => $resource]])); +var_dump(OPcache\volatile_fetch('nested-resource', 'missing')); +var_dump(OPcache\volatile_store_array(['nested-closure' => ['value' => $closure]])); +var_dump(OPcache\volatile_fetch('nested-closure', 'missing')); +var_dump(OPcache\volatile_store('object-resource', $resource_object)); +var_dump(OPcache\volatile_fetch('object-resource', 'missing')); +var_dump(OPcache\volatile_store('object-closure', $closure_object)); +var_dump(OPcache\volatile_fetch('object-closure', 'missing')); +var_dump(OPcache\volatile_store('spl-resource', $resource_fixed_array)); +var_dump(OPcache\volatile_fetch('spl-resource', 'missing')); +var_dump(OPcache\volatile_store('spl-closure', $closure_fixed_array)); +var_dump(OPcache\volatile_fetch('spl-closure', 'missing')); +var_dump(OPcache\volatile_store('array-object-resource', $resource_array_object)); +var_dump(OPcache\volatile_fetch('array-object-resource', 'missing')); +var_dump(OPcache\volatile_store('array-object-closure', $closure_array_object)); +var_dump(OPcache\volatile_fetch('array-object-closure', 'missing')); +StaticCacheUnsupportedSerializedPayload::$value = $resource; +var_dump(OPcache\volatile_store('serialized-resource', $serialized_payload)); +var_dump(OPcache\volatile_fetch('serialized-resource', 'missing')); +StaticCacheUnsupportedSerializedPayload::$value = $closure; +var_dump(OPcache\volatile_store('serialized-closure', $serialized_payload)); +var_dump(OPcache\volatile_fetch('serialized-closure', 'missing')); + +dump_type_error(static fn () => OPcache\volatile_fetch('missing', $resource)); +dump_type_error(static fn () => OPcache\volatile_fetch('missing', $closure)); + +dump_type_error(static fn () => OPcache\persistent_store('resource', $resource)); +dump_type_error(static fn () => OPcache\persistent_store('closure', $closure)); +dump_static_cache_exception(static fn () => OPcache\persistent_store_array(['nested-resource' => ['value' => $resource]])); +dump_static_cache_exception(static fn () => OPcache\persistent_store_array(['nested-closure' => ['value' => $closure]])); +dump_static_cache_exception(static fn () => OPcache\persistent_store('object-resource', $resource_object)); +dump_static_cache_exception(static fn () => OPcache\persistent_store('object-closure', $closure_object)); +dump_static_cache_exception(static fn () => OPcache\persistent_store('spl-resource', $resource_fixed_array)); +dump_static_cache_exception(static fn () => OPcache\persistent_store('spl-closure', $closure_fixed_array)); +dump_static_cache_exception(static fn () => OPcache\persistent_store('array-object-resource', $resource_array_object)); +dump_static_cache_exception(static fn () => OPcache\persistent_store('array-object-closure', $closure_array_object)); +StaticCacheUnsupportedSerializedPayload::$value = $resource; +dump_static_cache_exception(static fn () => OPcache\persistent_store('serialized-resource', $serialized_payload)); +StaticCacheUnsupportedSerializedPayload::$value = $closure; +dump_static_cache_exception(static fn () => OPcache\persistent_store('serialized-closure', $serialized_payload)); +dump_type_error(static fn () => OPcache\persistent_fetch('missing', $resource)); +dump_type_error(static fn () => OPcache\persistent_fetch('missing', $closure)); + +fclose($resource); + +?> +--EXPECT-- +OPcache\volatile_store(): Argument #2 ($value) must be of type object|array|string|int|float|bool|null, resource given +string(7) "missing" +OPcache\volatile_store(): Argument #2 ($value) must not be a Closure object +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +bool(false) +string(7) "missing" +OPcache\volatile_fetch(): Argument #2 ($default) must be of type object|array|string|int|float|bool|null, resource given +OPcache\volatile_fetch(): Argument #2 ($default) must not be a Closure object +OPcache\persistent_store(): Argument #2 ($value) must be of type object|array|string|int|float|bool|null, resource given +OPcache\persistent_store(): Argument #2 ($value) must not be a Closure object +OPcache\StaticCacheException: resources cannot be stored in the static cache +OPcache\StaticCacheException: Closure objects cannot be stored in the static cache +OPcache\StaticCacheException: resources cannot be stored in the static cache +OPcache\StaticCacheException: Closure objects cannot be stored in the static cache +OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache +OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache +OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache +OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache +OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache +OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache +OPcache\persistent_fetch(): Argument #2 ($default) must be of type object|array|string|int|float|bool|null, resource given +OPcache\persistent_fetch(): Argument #2 ($default) must not be a Closure object diff --git a/ext/opcache/tests/static_cache_volatile_cache_tracked_reference_assignment_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_tracked_reference_assignment_001.phpt new file mode 100644 index 000000000000..89b6cadf5ccb --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_tracked_reference_assignment_001.phpt @@ -0,0 +1,81 @@ +--TEST-- +OPcache VolatileStatic Immediate publishes scalar assignments through tracked references +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +reset +seed=one +read=one +mutate=two +read=two diff --git a/ext/opcache/tests/static_cache_volatile_cache_ttl_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_ttl_001.phpt new file mode 100644 index 000000000000..861a4144d8bc --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_ttl_001.phpt @@ -0,0 +1,28 @@ +--TEST-- +OPcache volatile cache TTL expiry and reuse +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + +--EXPECT-- +bool(true) +string(3) "one" +string(7) "expired" +bool(true) +string(3) "two" diff --git a/ext/opcache/tests/static_cache_volatile_cache_ttl_invalid_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_ttl_invalid_001.phpt new file mode 100644 index 000000000000..dfbfcdf9e258 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_ttl_invalid_001.phpt @@ -0,0 +1,35 @@ +--TEST-- +OPcache volatile cache store APIs reject negative TTL before storing entries +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- +getMessage(), "\n"; +} + +var_dump(OPcache\volatile_fetch('single', 'missing')); + +try { + OPcache\volatile_store_array(['array_first' => 'stored'], -1); +} catch (ValueError $exception) { + echo $exception->getMessage(), "\n"; +} + +var_dump(OPcache\volatile_fetch('array_first', 'missing')); + +?> +--EXPECT-- +OPcache\volatile_store(): Argument #3 ($ttl) must be greater than or equal to 0 +string(7) "missing" +OPcache\volatile_store_array(): Argument #2 ($ttl) must be greater than or equal to 0 +string(7) "missing" diff --git a/ext/opcache/tests/static_cache_volatile_cache_ttl_large_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_ttl_large_001.phpt new file mode 100644 index 000000000000..042877c94a26 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_ttl_large_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +OPcache volatile cache stores large TTL values without 32-bit expiry truncation +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + +--EXPECT-- +bool(true) +string(5) "value" diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_request_shared_graph_refs_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_request_shared_graph_refs_001.phpt new file mode 100644 index 000000000000..85aa31eb1617 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_request_shared_graph_refs_001.phpt @@ -0,0 +1,305 @@ +--TEST-- +OPcache volatile cache releases request-scoped shared graph refs across repeated ZTS requests +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- + MAX_COMMAND_OUTPUT_BYTES) { + return substr($contents, 0, MAX_COMMAND_OUTPUT_BYTES) . "\n...[output truncated]\n"; + } + + return $contents; +} + +function runCommand(array $command): array +{ + $stdoutFile = tempnam(sys_get_temp_dir(), 'opcache-zts-out-'); + $stderrFile = tempnam(sys_get_temp_dir(), 'opcache-zts-err-'); + if ($stdoutFile === false || $stderrFile === false) { + throw new RuntimeException('failed to create temporary files'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['file', $stdoutFile, 'w'], + 2 => ['file', $stderrFile, 'w'], + ]; + + $process = proc_open($command, $descriptorSpec, $pipes); + if (!is_resource($process)) { + @unlink($stdoutFile); + @unlink($stderrFile); + throw new RuntimeException('proc_open failed'); + } + + fclose($pipes[0]); + $status = proc_close($process); + + $stdout = readCommandOutput($stdoutFile); + $stderr = readCommandOutput($stderrFile); + @unlink($stdoutFile); + @unlink($stderrFile); + + return [$status, $stdout, $stderr]; +} + +function parseBuildFlags(string $flags): array +{ + $tokens = preg_split('/\s+/', trim($flags)); + if ($tokens === false) { + return []; + } + + return array_values(array_filter($tokens, static function (string $token): bool { + return $token !== '' && !str_contains($token, '$('); + })); +} + +function resolveBuildFlags(string $buildRoot, array $variables): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + $flags = []; + + if ($makefile === false) { + return $flags; + } + + foreach ($variables as $variable) { + if (preg_match('/^' . preg_quote($variable, '/') . '\s*=\s*(.+)$/m', $makefile, $matches)) { + $flags = array_merge($flags, parseBuildFlags($matches[1])); + } + } + + return $flags; +} + +function resolveBuildCommand(string $buildRoot, string $variable, array $fallback): array +{ + $value = getenv($variable); + if (is_string($value) && $value !== '') { + $tokens = parseBuildFlags($value); + if ($tokens !== []) { + return $tokens; + } + } + + $tokens = resolveBuildFlags($buildRoot, [$variable]); + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveBuildRoot(string $root): string +{ + $buildRoot = null; + $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; + if ($php) { + $php = realpath($php); + if ($php !== false) { + if (PHP_OS_FAMILY === 'Windows') { + $candidate = dirname($php); + if (glob($candidate . '/php*embed.lib')) { + $buildRoot = $candidate; + } + } else { + $candidate = dirname($php, 3); + if (file_exists($candidate . '/.libs/libphp.a') || file_exists($candidate . '/libs/libphp.a')) { + $buildRoot = $candidate; + } + } + } + } + + if ($buildRoot === null && PHP_OS_FAMILY !== 'Windows' && (file_exists($root . '/.libs/libphp.a') || file_exists($root . '/libs/libphp.a'))) { + $buildRoot = $root; + } + + if ($buildRoot === null) { + throw new RuntimeException('Failed to resolve build root'); + } + + return $buildRoot; +} + +function resolveExtraLibs(string $buildRoot): array +{ + if (PHP_OS_FAMILY === 'Windows') { + return []; + } + + $makefile = @file_get_contents($buildRoot . '/Makefile'); + if ($makefile !== false && preg_match('/^EXTRA_LIBS\s*=\s*(.+)$/m', $makefile, $matches)) { + $libs = preg_split('/\s+/', trim($matches[1])); + if ($libs !== false && $libs !== ['']) { + return $libs; + } + } + + return [ + '-lm', + '-lpthread', + '-ldl', + '-lresolv', + '-lutil', + ]; +} + +function resolveWindowsEmbedLib(string $buildRoot): string +{ + $candidates = glob($buildRoot . '/php*embed.lib'); + if ($candidates !== false && $candidates !== []) { + return $candidates[0]; + } + + throw new RuntimeException('Failed to resolve Windows embed library'); +} + +$root = realpath(__DIR__ . '/../../..'); +if ($root === false) { + throw new RuntimeException('Failed to resolve source root'); +} + +$buildRoot = resolveBuildRoot($root); +$source = __DIR__ . '/helpers/volatile_cache_zts_request_shared_graph_refs_001.c'; +$binary = __DIR__ . '/helpers/volatile_cache_zts_request_shared_graph_refs_001' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); +$compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$extraLibs = resolveExtraLibs($buildRoot); + +if (PHP_OS_FAMILY === 'Windows') { + $compileCommand = [ + ...$compiler, + '/nologo', + '/EHsc', + '/I' . $buildRoot, + '/I' . $buildRoot . '/main', + '/I' . $buildRoot . '/Zend', + '/I' . $buildRoot . '/TSRM', + '/I' . $buildRoot . '/sapi/embed', + '/I' . $buildRoot . '/ext/opcache', + '/I' . $buildRoot . '/ext/date/lib', + '/I' . $buildRoot . '/ext/opcache', + '/I' . $root, + '/I' . $root . '/main', + '/I' . $root . '/Zend', + '/I' . $root . '/TSRM', + '/I' . $root . '/sapi/embed', + '/I' . $root . '/ext/date/lib', + '/I' . $root . '/ext/opcache', + '/Fe:' . $binary, + $source, + resolveWindowsEmbedLib($buildRoot), + ]; +} else { + $compileCommand = [ + ...$compiler, + ...$buildFlags, + '-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '-DHAVE_CONFIG_H', + '-I' . $buildRoot, + '-I' . $buildRoot . '/main', + '-I' . $buildRoot . '/Zend', + '-I' . $buildRoot . '/TSRM', + '-I' . $buildRoot . '/sapi/embed', + '-I' . $buildRoot . '/ext/opcache', + '-I' . $buildRoot . '/ext/date/lib', + '-I' . $root, + '-I' . $root . '/main', + '-I' . $root . '/Zend', + '-I' . $root . '/TSRM', + '-I' . $root . '/sapi/embed', + '-I' . $root . '/ext/date/lib', + '-I' . $root . '/ext/opcache', + '-o', + $binary, + $source, + file_exists($buildRoot . '/.libs/libphp.a') ? $buildRoot . '/.libs/libphp.a' : $buildRoot . '/libs/libphp.a', + ]; + $compileCommand = array_merge($compileCommand, $extraLibs); +} + +[$status, $stdout, $stderr] = runCommand($compileCommand); +if ($status !== 0) { + echo $stdout, $stderr; + exit(1); +} + +@chmod($binary, 0755); +[$status, $stdout, $stderr] = runCommand([$binary]); +echo $stdout; +if ($status !== 0) { + echo $stderr; + exit(1); +} + +?> +--CLEAN-- + +--EXPECT-- +ok diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_001.phpt new file mode 100644 index 000000000000..39faffc62df7 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_001.phpt @@ -0,0 +1,289 @@ +--TEST-- +OPcache volatile cache is shared across threads on ZTS builds +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- + MAX_COMMAND_OUTPUT_BYTES) { + return substr($contents, 0, MAX_COMMAND_OUTPUT_BYTES) . "\n...[output truncated]\n"; + } + + return $contents; +} + +function runCommand(array $command): array +{ + $stdoutFile = tempnam(sys_get_temp_dir(), 'opcache-zts-out-'); + $stderrFile = tempnam(sys_get_temp_dir(), 'opcache-zts-err-'); + if ($stdoutFile === false || $stderrFile === false) { + throw new RuntimeException('failed to create temporary files'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['file', $stdoutFile, 'w'], + 2 => ['file', $stderrFile, 'w'], + ]; + + $process = proc_open($command, $descriptorSpec, $pipes); + if (!is_resource($process)) { + @unlink($stdoutFile); + @unlink($stderrFile); + throw new RuntimeException('proc_open failed'); + } + + fclose($pipes[0]); + $status = proc_close($process); + + $stdout = readCommandOutput($stdoutFile); + $stderr = readCommandOutput($stderrFile); + @unlink($stdoutFile); + @unlink($stderrFile); + + return [$status, $stdout, $stderr]; +} + +function parseBuildFlags(string $flags): array +{ + $tokens = preg_split('/\s+/', trim($flags)); + if ($tokens === false) { + return []; + } + + return array_values(array_filter($tokens, static function (string $token): bool { + return $token !== '' && !str_contains($token, '$('); + })); +} + +function resolveBuildFlags(string $buildRoot, array $variables): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + $flags = []; + + if ($makefile === false) { + return $flags; + } + + foreach ($variables as $variable) { + if (preg_match('/^' . preg_quote($variable, '/') . '\s*=\s*(.+)$/m', $makefile, $matches)) { + $flags = array_merge($flags, parseBuildFlags($matches[1])); + } + } + + return $flags; +} + +function resolveBuildCommand(string $buildRoot, string $variable, array $fallback): array +{ + $value = getenv($variable); + if (is_string($value) && $value !== '') { + $tokens = parseBuildFlags($value); + if ($tokens !== []) { + return $tokens; + } + } + + $tokens = resolveBuildFlags($buildRoot, [$variable]); + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveExtraLibs(string $buildRoot): array +{ + if (PHP_OS_FAMILY === 'Windows') { + return []; + } + + $makefile = @file_get_contents($buildRoot . '/Makefile'); + if ($makefile !== false && preg_match('/^EXTRA_LIBS\s*=\s*(.+)$/m', $makefile, $matches)) { + $libs = preg_split('/\s+/', trim($matches[1])); + if ($libs !== false && $libs !== ['']) { + return $libs; + } + } + + return [ + '-lm', + '-lpthread', + '-ldl', + '-lresolv', + '-lutil', + ]; +} + +function resolveWindowsEmbedLib(string $buildRoot): string +{ + $candidates = glob($buildRoot . '/php*embed.lib'); + if ($candidates !== false && $candidates !== []) { + return $candidates[0]; + } + + throw new RuntimeException('Failed to resolve Windows embed library'); +} + +$root = realpath(__DIR__ . '/../../..'); +$buildRoot = null; +$php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; +if ($php) { + $php = realpath($php); + if ($php !== false) { + if (PHP_OS_FAMILY === 'Windows') { + $candidate = dirname($php); + if (glob($candidate . '/php*embed.lib')) { + $buildRoot = $candidate; + } + } else { + $candidate = dirname($php, 3); + if (file_exists($candidate . '/.libs/libphp.a') || file_exists($candidate . '/libs/libphp.a')) { + $buildRoot = $candidate; + } + } + } +} + +if ($buildRoot === null && $root !== false && PHP_OS_FAMILY !== 'Windows' && (file_exists($root . '/.libs/libphp.a') || file_exists($root . '/libs/libphp.a'))) { + $buildRoot = $root; +} + +if ($root === false || $buildRoot === null) { + throw new RuntimeException('Failed to resolve build root'); +} + +$source = __DIR__ . '/helpers/volatile_cache_zts_threads_001.c'; +$binary = __DIR__ . '/helpers/volatile_cache_zts_threads_001' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); +$compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$extraLibs = resolveExtraLibs($buildRoot); + +if (PHP_OS_FAMILY === 'Windows') { + $compileCommand = [ + ...$compiler, + '/nologo', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/DHAVE_CONFIG_H', + '/I' . $buildRoot, + '/I' . $buildRoot . '/main', + '/I' . $buildRoot . '/Zend', + '/I' . $buildRoot . '/TSRM', + '/I' . $buildRoot . '/sapi/embed', + '/I' . $root, + '/I' . $root . '/main', + '/I' . $root . '/Zend', + '/I' . $root . '/TSRM', + '/I' . $root . '/sapi/embed', + '/Fe:' . $binary, + $source, + '/link', + resolveWindowsEmbedLib($buildRoot), + ]; +} else { + $compileCommand = [ + ...$compiler, + ...$buildFlags, + '-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '-DHAVE_CONFIG_H', + '-I' . $buildRoot, + '-I' . $buildRoot . '/main', + '-I' . $buildRoot . '/Zend', + '-I' . $buildRoot . '/TSRM', + '-I' . $buildRoot . '/sapi/embed', + '-I' . $root, + '-I' . $root . '/main', + '-I' . $root . '/Zend', + '-I' . $root . '/TSRM', + '-I' . $root . '/sapi/embed', + '-o', + $binary, + $source, + $root . '/sapi/embed/php_embed.c', + file_exists($buildRoot . '/.libs/libphp.a') ? $buildRoot . '/.libs/libphp.a' : $buildRoot . '/libs/libphp.a', + ]; + $compileCommand = array_merge($compileCommand, $extraLibs); +} + +[$status, $stdout, $stderr] = runCommand($compileCommand); +if ($status !== 0) { + echo $stdout, $stderr; + exit(1); +} + +@chmod($binary, 0755); +[$status, $stdout, $stderr] = runCommand([$binary]); +echo $stdout; +if ($status !== 0) { + echo $stderr; + exit(1); +} + +?> +--CLEAN-- + +--EXPECT-- +ok diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt new file mode 100644 index 000000000000..1c5f3de00261 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt @@ -0,0 +1,310 @@ +--TEST-- +OPcache volatile cache validates __DirectCacheSafe and copy-on-mutate behavior across ZTS threads +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- + MAX_COMMAND_OUTPUT_BYTES) { + return substr($contents, 0, MAX_COMMAND_OUTPUT_BYTES) . "\n...[output truncated]\n"; + } + + return $contents; +} + +function runCommand(array $command): array +{ + $stdoutFile = tempnam(sys_get_temp_dir(), 'opcache-zts-out-'); + $stderrFile = tempnam(sys_get_temp_dir(), 'opcache-zts-err-'); + if ($stdoutFile === false || $stderrFile === false) { + throw new RuntimeException('failed to create temporary files'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['file', $stdoutFile, 'w'], + 2 => ['file', $stderrFile, 'w'], + ]; + + $process = proc_open($command, $descriptorSpec, $pipes); + if (!is_resource($process)) { + @unlink($stdoutFile); + @unlink($stderrFile); + throw new RuntimeException('proc_open failed'); + } + + fclose($pipes[0]); + $status = proc_close($process); + + $stdout = readCommandOutput($stdoutFile); + $stderr = readCommandOutput($stderrFile); + @unlink($stdoutFile); + @unlink($stderrFile); + + return [$status, $stdout, $stderr]; +} + +function parseBuildFlags(string $flags): array +{ + $tokens = preg_split('/\s+/', trim($flags)); + if ($tokens === false) { + return []; + } + + return array_values(array_filter($tokens, static function (string $token): bool { + return $token !== '' && !str_contains($token, '$('); + })); +} + +function resolveBuildFlags(string $buildRoot, array $variables): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + $flags = []; + + if ($makefile === false) { + return $flags; + } + + foreach ($variables as $variable) { + if (preg_match('/^' . preg_quote($variable, '/') . '\s*=\s*(.+)$/m', $makefile, $matches)) { + $flags = array_merge($flags, parseBuildFlags($matches[1])); + } + } + + return $flags; +} + +function resolveBuildCommand(string $buildRoot, string $variable, array $fallback): array +{ + $value = getenv($variable); + if (is_string($value) && $value !== '') { + $tokens = parseBuildFlags($value); + if ($tokens !== []) { + return $tokens; + } + } + + $tokens = resolveBuildFlags($buildRoot, [$variable]); + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveBuildRoot(string $root): string +{ + $buildRoot = null; + $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; + if ($php) { + $php = realpath($php); + if ($php !== false) { + if (PHP_OS_FAMILY === 'Windows') { + $candidate = dirname($php); + if (glob($candidate . '/php*embed.lib')) { + $buildRoot = $candidate; + } + } else { + $candidate = dirname($php, 3); + if (file_exists($candidate . '/.libs/libphp.a') || file_exists($candidate . '/libs/libphp.a')) { + $buildRoot = $candidate; + } + } + } + } + + if ($buildRoot === null && PHP_OS_FAMILY !== 'Windows' && (file_exists($root . '/.libs/libphp.a') || file_exists($root . '/libs/libphp.a'))) { + $buildRoot = $root; + } + + if ($buildRoot === null) { + throw new RuntimeException('Failed to resolve build root'); + } + + return $buildRoot; +} + +function resolveExtraLibs(string $buildRoot): array +{ + if (PHP_OS_FAMILY === 'Windows') { + return []; + } + + $makefile = @file_get_contents($buildRoot . '/Makefile'); + if ($makefile !== false && preg_match('/^EXTRA_LIBS\s*=\s*(.+)$/m', $makefile, $matches)) { + $libs = preg_split('/\s+/', trim($matches[1])); + if ($libs !== false && $libs !== ['']) { + return $libs; + } + } + + return [ + '-lm', + '-lpthread', + '-ldl', + '-lresolv', + '-lutil', + ]; +} + +function resolveWindowsEmbedLib(string $buildRoot): string +{ + $candidates = glob($buildRoot . '/php*embed.lib'); + if ($candidates !== false && $candidates !== []) { + return $candidates[0]; + } + + throw new RuntimeException('Failed to resolve Windows embed library'); +} + +function runScenario(string $binary, string $scenario, string $label): void +{ + [$status, $stdout, $stderr] = runCommand([$binary, $scenario]); + if ($status !== 0) { + echo $stdout, $stderr; + exit(1); + } + + echo $label, ': ', trim($stdout), "\n"; +} + +$root = realpath(__DIR__ . '/../../..'); +if ($root === false) { + throw new RuntimeException('Failed to resolve source root'); +} + +$buildRoot = resolveBuildRoot($root); +$source = __DIR__ . '/helpers/volatile_cache_zts_threads_002.c'; +$binary = __DIR__ . '/helpers/volatile_cache_zts_threads_002' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); +$compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$extraLibs = resolveExtraLibs($buildRoot); + +if (PHP_OS_FAMILY === 'Windows') { + $compileCommand = [ + ...$compiler, + '/nologo', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/DHAVE_CONFIG_H', + '/I' . $buildRoot, + '/I' . $buildRoot . '/main', + '/I' . $buildRoot . '/Zend', + '/I' . $buildRoot . '/TSRM', + '/I' . $buildRoot . '/sapi/embed', + '/I' . $root, + '/I' . $root . '/main', + '/I' . $root . '/Zend', + '/I' . $root . '/TSRM', + '/I' . $root . '/sapi/embed', + '/Fe:' . $binary, + $source, + '/link', + resolveWindowsEmbedLib($buildRoot), + ]; +} else { + $compileCommand = [ + ...$compiler, + ...$buildFlags, + '-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '-DHAVE_CONFIG_H', + '-I' . $buildRoot, + '-I' . $buildRoot . '/main', + '-I' . $buildRoot . '/Zend', + '-I' . $buildRoot . '/TSRM', + '-I' . $buildRoot . '/sapi/embed', + '-I' . $root, + '-I' . $root . '/main', + '-I' . $root . '/Zend', + '-I' . $root . '/TSRM', + '-I' . $root . '/sapi/embed', + '-o', + $binary, + $source, + $root . '/sapi/embed/php_embed.c', + file_exists($buildRoot . '/.libs/libphp.a') ? $buildRoot . '/.libs/libphp.a' : $buildRoot . '/libs/libphp.a', + ]; + $compileCommand = array_merge($compileCommand, $extraLibs); +} + +[$status, $stdout, $stderr] = runCommand($compileCommand); +if ($status !== 0) { + echo $stdout, $stderr; + exit(1); +} + +@chmod($binary, 0755); + +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_002_safe_direct.inc', 'safe_direct'); +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_002_materialization_plain.inc', 'materialization_plain'); +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_002_materialization_nested.inc', 'materialization_nested'); + +?> +--CLEAN-- + +--EXPECT-- +safe_direct: ok +materialization_plain: ok +materialization_nested: ok diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_003.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_003.phpt new file mode 100644 index 000000000000..74a7b18bc8eb --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_003.phpt @@ -0,0 +1,316 @@ +--TEST-- +OPcache volatile cache request-local lookup cache sees cross-thread updates on next fetch +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- + MAX_COMMAND_OUTPUT_BYTES) { + return substr($contents, 0, MAX_COMMAND_OUTPUT_BYTES) . "\n...[output truncated]\n"; + } + + return $contents; +} + +function runCommand(array $command): array +{ + $stdoutFile = tempnam(sys_get_temp_dir(), 'opcache-zts-out-'); + $stderrFile = tempnam(sys_get_temp_dir(), 'opcache-zts-err-'); + if ($stdoutFile === false || $stderrFile === false) { + throw new RuntimeException('failed to create temporary files'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], + 1 => ['file', $stdoutFile, 'w'], + 2 => ['file', $stderrFile, 'w'], + ]; + + $process = proc_open($command, $descriptorSpec, $pipes); + if (!is_resource($process)) { + @unlink($stdoutFile); + @unlink($stderrFile); + throw new RuntimeException('proc_open failed'); + } + + fclose($pipes[0]); + $status = proc_close($process); + + $stdout = readCommandOutput($stdoutFile); + $stderr = readCommandOutput($stderrFile); + @unlink($stdoutFile); + @unlink($stderrFile); + + return [$status, $stdout, $stderr]; +} + +function parseBuildFlags(string $flags): array +{ + $tokens = preg_split('/\s+/', trim($flags)); + if ($tokens === false) { + return []; + } + + return array_values(array_filter($tokens, static function (string $token): bool { + return $token !== '' && !str_contains($token, '$('); + })); +} + +function resolveBuildFlags(string $buildRoot, array $variables): array +{ + $makefile = @file_get_contents($buildRoot . '/Makefile'); + $flags = []; + + if ($makefile === false) { + return $flags; + } + + foreach ($variables as $variable) { + if (preg_match('/^' . preg_quote($variable, '/') . '\s*=\s*(.+)$/m', $makefile, $matches)) { + $flags = array_merge($flags, parseBuildFlags($matches[1])); + } + } + + return $flags; +} + +function resolveBuildCommand(string $buildRoot, string $variable, array $fallback): array +{ + $value = getenv($variable); + if (is_string($value) && $value !== '') { + $tokens = parseBuildFlags($value); + if ($tokens !== []) { + return $tokens; + } + } + + $tokens = resolveBuildFlags($buildRoot, [$variable]); + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveBuildRoot(string $root): string +{ + $buildRoot = null; + $php = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; + if ($php) { + $php = realpath($php); + if ($php !== false) { + if (PHP_OS_FAMILY === 'Windows') { + $candidate = dirname($php); + if (glob($candidate . '/php*embed.lib')) { + $buildRoot = $candidate; + } + } else { + $candidate = dirname($php, 3); + if (file_exists($candidate . '/.libs/libphp.a') || file_exists($candidate . '/libs/libphp.a')) { + $buildRoot = $candidate; + } + } + } + } + + if ($buildRoot === null && PHP_OS_FAMILY !== 'Windows' && (file_exists($root . '/.libs/libphp.a') || file_exists($root . '/libs/libphp.a'))) { + $buildRoot = $root; + } + + if ($buildRoot === null) { + throw new RuntimeException('Failed to resolve build root'); + } + + return $buildRoot; +} + +function resolveExtraLibs(string $buildRoot): array +{ + if (PHP_OS_FAMILY === 'Windows') { + return []; + } + + $makefile = @file_get_contents($buildRoot . '/Makefile'); + if ($makefile !== false && preg_match('/^EXTRA_LIBS\s*=\s*(.+)$/m', $makefile, $matches)) { + $libs = preg_split('/\s+/', trim($matches[1])); + if ($libs !== false && $libs !== ['']) { + return $libs; + } + } + + return [ + '-lm', + '-lpthread', + '-ldl', + '-lresolv', + '-lutil', + ]; +} + +function resolveWindowsEmbedLib(string $buildRoot): string +{ + $candidates = glob($buildRoot . '/php*embed.lib'); + if ($candidates !== false && $candidates !== []) { + return $candidates[0]; + } + + throw new RuntimeException('Failed to resolve Windows embed library'); +} + +function runScenario(string $binary, string $scenario, string $label): void +{ + [$status, $stdout, $stderr] = runCommand([$binary, $scenario]); + if ($status !== 0) { + echo $stdout, $stderr; + exit(1); + } + + echo $label, ': ', trim($stdout), "\n"; +} + +$root = realpath(__DIR__ . '/../../..'); +if ($root === false) { + throw new RuntimeException('Failed to resolve source root'); +} + +$buildRoot = resolveBuildRoot($root); +$source = __DIR__ . '/helpers/volatile_cache_zts_threads_003.c'; +$binary = __DIR__ . '/helpers/volatile_cache_zts_threads_003' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); +$compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$extraLibs = resolveExtraLibs($buildRoot); + +if (PHP_OS_FAMILY === 'Windows') { + $compileCommand = [ + ...$compiler, + '/nologo', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/DHAVE_CONFIG_H', + '/I' . $buildRoot, + '/I' . $buildRoot . '/main', + '/I' . $buildRoot . '/Zend', + '/I' . $buildRoot . '/TSRM', + '/I' . $buildRoot . '/sapi/embed', + '/I' . $root, + '/I' . $root . '/main', + '/I' . $root . '/Zend', + '/I' . $root . '/TSRM', + '/I' . $root . '/sapi/embed', + '/Fe:' . $binary, + $source, + '/link', + resolveWindowsEmbedLib($buildRoot), + ]; +} else { + $compileCommand = [ + ...$compiler, + ...$buildFlags, + '-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '-DHAVE_CONFIG_H', + '-I' . $buildRoot, + '-I' . $buildRoot . '/main', + '-I' . $buildRoot . '/Zend', + '-I' . $buildRoot . '/TSRM', + '-I' . $buildRoot . '/sapi/embed', + '-I' . $root, + '-I' . $root . '/main', + '-I' . $root . '/Zend', + '-I' . $root . '/TSRM', + '-I' . $root . '/sapi/embed', + '-o', + $binary, + $source, + $root . '/sapi/embed/php_embed.c', + file_exists($buildRoot . '/.libs/libphp.a') ? $buildRoot . '/.libs/libphp.a' : $buildRoot . '/libs/libphp.a', + ]; + $compileCommand = array_merge($compileCommand, $extraLibs); +} + +[$status, $stdout, $stderr] = runCommand($compileCommand); +if ($status !== 0) { + echo $stdout, $stderr; + exit(1); +} + +@chmod($binary, 0755); + +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_003_hit.inc', 'lookup_hit'); +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_003_miss.inc', 'lookup_miss'); +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_003_delete.inc', 'lookup_delete'); +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_003_clear.inc', 'lookup_clear'); +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_003_materialized_clear.inc', 'materialized_clear'); +runScenario($binary, __DIR__ . '/helpers/volatile_cache_zts_threads_003_volatile_static_class.inc', 'volatile_static_class'); + +?> +--CLEAN-- + +--EXPECT-- +lookup_hit: ok +lookup_miss: ok +lookup_delete: ok +lookup_clear: ok +materialized_clear: ok +volatile_static_class: ok diff --git a/ext/opcache/tests/static_cache_volatile_static_clear_after_access_001.phpt b/ext/opcache/tests/static_cache_volatile_static_clear_after_access_001.phpt new file mode 100644 index 000000000000..5c6c08eb5b6d --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_clear_after_access_001.phpt @@ -0,0 +1,61 @@ +--TEST-- +OPcache VolatileStatic is not republished after volatile_clear() in the same request +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1 +2,2 +3,3 +clear +1,1 diff --git a/ext/opcache/tests/static_cache_volatile_static_clear_isolated_from_persistent_static_001.phpt b/ext/opcache/tests/static_cache_volatile_static_clear_isolated_from_persistent_static_001.phpt new file mode 100644 index 000000000000..4aa933f35298 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_clear_isolated_from_persistent_static_001.phpt @@ -0,0 +1,71 @@ +--TEST-- +OPcache VolatileStatic persists through volatile cache and can be cleared independently of PersistentStatic +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +1,1,1,1 +2,2,2,2 +clear +1,1,3,3 diff --git a/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt b/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt new file mode 100644 index 000000000000..2c18d31cf0b2 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt @@ -0,0 +1,327 @@ +--TEST-- +OPcache VolatileStatic strategies control publication timing without changing volatile-cache backend +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +ttl, ':', $defaultAttribute->strategy->value, ',', $trackingAttribute->ttl, ':', $trackingAttribute->strategy->value, ',', $trackingAttribute->strategy->name, ',', $ttlAttribute->ttl, ':', $ttlAttribute->strategy->value, "\n"; +echo 'cases=', implode(',', array_map(static fn (OPcache\CacheStrategy $case): string => $case->name . ':' . $case->value, OPcache\CacheStrategy::cases())), "\n"; + +try { + new OPcache\VolatileStatic(ttl: -1); +} catch (ValueError) { + echo "ttl-value-error\n"; +} + +file_put_contents(__DIR__ . '/volatile_static_strategy_001.php', <<<'PHP' +events[] = 'x'; +} + +#[OPcache\VolatileStatic] +class CachedStrategyClassDefault +{ + public static ?CachedStrategyPayload $property = null; + + public static function property(): CachedStrategyPayload + { + self::$property ??= cached_strategy_payload('class_default_property'); + return self::$property; + } + + public static function method(): CachedStrategyPayload + { + static $value = null; + + $value ??= cached_strategy_payload('class_default_method'); + return $value; + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Immediate)] +class CachedStrategyClassImmediate +{ + public static ?CachedStrategyPayload $property = null; + + public static function property(): CachedStrategyPayload + { + self::$property ??= cached_strategy_payload('class_immediate_property'); + return self::$property; + } + + public static function method(): CachedStrategyPayload + { + static $value = null; + + $value ??= cached_strategy_payload('class_immediate_method'); + return $value; + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class CachedStrategyClassTracking +{ + public static ?CachedStrategyPayload $property = null; + + public static function property(): CachedStrategyPayload + { + self::$property ??= cached_strategy_payload('class_tracking_property'); + return self::$property; + } + + public static function method(): CachedStrategyPayload + { + static $value = null; + + $value ??= cached_strategy_payload('class_tracking_method'); + return $value; + } +} + +class CachedStrategyPropertyDefault +{ + #[OPcache\VolatileStatic] + public static ?CachedStrategyPayload $value = null; + + public static function value(): CachedStrategyPayload + { + self::$value ??= cached_strategy_payload('property_default'); + return self::$value; + } +} + +class CachedStrategyPropertyImmediate +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Immediate)] + public static ?CachedStrategyPayload $value = null; + + public static function value(): CachedStrategyPayload + { + self::$value ??= cached_strategy_payload('property_immediate'); + return self::$value; + } +} + +class CachedStrategyPropertyTracking +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?CachedStrategyPayload $value = null; + + public static function value(): CachedStrategyPayload + { + self::$value ??= cached_strategy_payload('property_tracking'); + return self::$value; + } +} + +class CachedStrategyMethodDefault +{ + #[OPcache\VolatileStatic] + public static function value(): CachedStrategyPayload + { + static $value = null; + + $value ??= cached_strategy_payload('method_default'); + return $value; + } +} + +class CachedStrategyMethodImmediate +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Immediate)] + public static function value(): CachedStrategyPayload + { + static $value = null; + + $value ??= cached_strategy_payload('method_immediate'); + return $value; + } +} + +class CachedStrategyMethodTracking +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function value(): CachedStrategyPayload + { + static $value = null; + + $value ??= cached_strategy_payload('method_tracking'); + return $value; + } +} + +function cached_strategy_values(string $case): array +{ + return match ($case) { + 'class_default' => [CachedStrategyClassDefault::property(), CachedStrategyClassDefault::method()], + 'class_immediate' => [CachedStrategyClassImmediate::property(), CachedStrategyClassImmediate::method()], + 'class_tracking' => [CachedStrategyClassTracking::property(), CachedStrategyClassTracking::method()], + 'property_default' => [CachedStrategyPropertyDefault::value()], + 'property_immediate' => [CachedStrategyPropertyImmediate::value()], + 'property_tracking' => [CachedStrategyPropertyTracking::value()], + 'method_default' => [CachedStrategyMethodDefault::value()], + 'method_immediate' => [CachedStrategyMethodImmediate::value()], + 'method_tracking' => [CachedStrategyMethodTracking::value()], + default => throw new RuntimeException('unknown case'), + }; +} + +function cached_strategy_dump(string $case, array $values): void +{ + echo $case, ':', implode('|', array_map( + static fn (CachedStrategyPayload $payload): int => count($payload->events), + $values, + )), "\n"; +} + +$action = $_GET['action'] ?? 'read'; +$case = $_GET['case'] ?? 'class_default'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + OPcache\persistent_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +$values = cached_strategy_values($case); + +if ($action === 'seed' || $action === 'mutate_after_fetch') { + foreach ($values as $value) { + cached_strategy_mutate($value); + } +} + +cached_strategy_dump($case, $values); + +if ($action === 'read') { + echo 'cache=', OPcache\volatile_cache_info()['entry_count'], ',', OPcache\persistent_cache_info()['entry_count'], "\n"; +} +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_strategy_001.php'; +$cases = [ + 'class_default', + 'class_immediate', + 'class_tracking', + 'property_default', + 'property_immediate', + 'property_tracking', + 'method_default', + 'method_immediate', + 'method_tracking', +]; + +foreach ($cases as $case) { + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=seed&case=' . $case); + echo file_get_contents($base . '?action=read&case=' . $case); + echo file_get_contents($base . '?action=mutate_after_fetch&case=' . $case); + echo file_get_contents($base . '?action=read&case=' . $case); +} + +?> +--CLEAN-- + +--EXPECT-- +attribute=0:0,0:1,Tracking,1:0 +cases=Immediate:0,Tracking:1 +ttl-value-error +reset +class_default:1|1 +class_default:0|0 +cache=1,0 +class_default:1|1 +class_default:0|0 +cache=1,0 +reset +class_immediate:1|1 +class_immediate:0|0 +cache=1,0 +class_immediate:1|1 +class_immediate:0|0 +cache=1,0 +reset +class_tracking:1|1 +class_tracking:1|1 +cache=1,0 +class_tracking:2|2 +class_tracking:2|2 +cache=1,0 +reset +property_default:1 +property_default:0 +cache=1,0 +property_default:1 +property_default:0 +cache=1,0 +reset +property_immediate:1 +property_immediate:0 +cache=1,0 +property_immediate:1 +property_immediate:0 +cache=1,0 +reset +property_tracking:1 +property_tracking:1 +cache=1,0 +property_tracking:2 +property_tracking:2 +cache=1,0 +reset +method_default:1 +method_default:0 +cache=1,0 +method_default:1 +method_default:0 +cache=1,0 +reset +method_immediate:1 +method_immediate:0 +cache=1,0 +method_immediate:1 +method_immediate:0 +cache=1,0 +reset +method_tracking:1 +method_tracking:1 +cache=1,0 +method_tracking:2 +method_tracking:2 +cache=1,0 diff --git a/ext/opcache/tests/static_cache_volatile_static_tracking_alias_value_kinds_001.phpt b/ext/opcache/tests/static_cache_volatile_static_tracking_alias_value_kinds_001.phpt new file mode 100644 index 000000000000..66d986f34f7a --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_tracking_alias_value_kinds_001.phpt @@ -0,0 +1,185 @@ +--TEST-- +OPcache VolatileStatic tracking follows PHP alias semantics across scalar and array assignments +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + '1']; + VolatileStaticTrackingArrayCopyState::$value = $value; + $value['bar'] = '2'; + echo 'seed=', VolatileStaticTrackingArrayCopyState::$value['bar'], "\n"; + return; + + case 'array_reference': + $value = '1'; + $array = ['bar' => &$value]; + VolatileStaticTrackingArrayReferenceState::$value = $array; + $value = '2'; + echo 'seed=', VolatileStaticTrackingArrayReferenceState::$value['bar'], "\n"; + return; + + case 'scalar_reference_call': + $value = '1'; + VolatileStaticTrackingScalarReferenceCallState::$value = $value; + volatile_static_tracking_alias_mutate_string($value); + echo 'seed=', VolatileStaticTrackingScalarReferenceCallState::$value, "\n"; + return; + + case 'object_reference': + $value = new VolatileStaticTrackingObjectReferenceValue(); + $value->bar = '1'; + VolatileStaticTrackingObjectReferenceState::$value = $value; + $value->bar = '2'; + echo 'seed=', VolatileStaticTrackingObjectReferenceState::$value?->bar ?? 'null', "\n"; + return; + + default: + throw new RuntimeException('unknown case'); + } +} + +function volatile_static_tracking_alias_read(string $case): void +{ + switch ($case) { + case 'scalar_copy': + echo 'read=', VolatileStaticTrackingScalarCopyState::$value ?? 'null', "\n"; + return; + + case 'array_copy': + echo 'read=', VolatileStaticTrackingArrayCopyState::$value['bar'] ?? 'null', "\n"; + return; + + case 'array_reference': + echo 'read=', VolatileStaticTrackingArrayReferenceState::$value['bar'] ?? 'null', "\n"; + return; + + case 'scalar_reference_call': + echo 'read=', VolatileStaticTrackingScalarReferenceCallState::$value ?? 'null', "\n"; + return; + + case 'object_reference': + echo 'read=', VolatileStaticTrackingObjectReferenceState::$value?->bar ?? 'null', "\n"; + return; + + default: + throw new RuntimeException('unknown case'); + } +} + +$action = $_GET['action'] ?? 'read'; +$case = $_GET['case'] ?? 'scalar_copy'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + volatile_static_tracking_alias_seed($case); + return; +} + +volatile_static_tracking_alias_read($case); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/static_cache_volatile_static_tracking_alias_value_kinds_001.php'; +foreach (['scalar_copy', 'array_copy', 'array_reference', 'scalar_reference_call', 'object_reference'] as $case) { + echo file_get_contents($base . '?action=reset'); + echo $case, "\n"; + echo file_get_contents($base . '?action=seed&case=' . $case); + echo file_get_contents($base . '?action=read&case=' . $case); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +scalar_copy +seed=1 +read=1 +reset +array_copy +seed=1 +read=1 +reset +array_reference +seed=2 +read=2 +reset +scalar_reference_call +seed=1 +read=1 +reset +object_reference +seed=2 +read=2 diff --git a/ext/opcache/tests/static_cache_volatile_static_tracking_clear_invalidate_after_access_001.phpt b/ext/opcache/tests/static_cache_volatile_static_tracking_clear_invalidate_after_access_001.phpt new file mode 100644 index 000000000000..481642f59414 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_tracking_clear_invalidate_after_access_001.phpt @@ -0,0 +1,291 @@ +--TEST-- +OPcache VolatileStatic tracking reference graphs are not republished after volatile_clear() or opcache_invalidate() in the same request +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +holder = new stdClass(); + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingClearInvalidateClassState +{ + public static ?TrackingClearInvalidatePayload $payload = null; +} + +class TrackingClearInvalidatePropertyState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?TrackingClearInvalidatePayload $payload = null; +} + +class TrackingClearInvalidateMethodState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function payload(?TrackingClearInvalidatePayload $payload = null): ?TrackingClearInvalidatePayload + { + static $value = null; + + if ($payload !== null) { + $value = $payload; + } + + return $value; + } +} + +function tracking_clear_invalidate_set(string $kind, ?TrackingClearInvalidatePayload $payload): void +{ + switch ($kind) { + case 'class': + TrackingClearInvalidateClassState::$payload = $payload; + return; + + case 'property': + TrackingClearInvalidatePropertyState::$payload = $payload; + return; + + case 'method': + TrackingClearInvalidateMethodState::payload($payload); + return; + + default: + throw new RuntimeException('unknown kind'); + } +} + +function tracking_clear_invalidate_get(string $kind): ?TrackingClearInvalidatePayload +{ + return match ($kind) { + 'class' => TrackingClearInvalidateClassState::$payload, + 'property' => TrackingClearInvalidatePropertyState::$payload, + 'method' => TrackingClearInvalidateMethodState::payload(), + default => throw new RuntimeException('unknown kind'), + }; +} + +function tracking_clear_invalidate_apply(TrackingClearInvalidatePayload $payload, string $nodeName, string $childName, string $label): void +{ + $payload->objectRef->name = $nodeName; + $payload->objectRef->events[] = $label . '-object'; + $payload->arrayRef['child']->name = $childName; + $payload->arrayRef['child']->events[] = $label . '-child'; + $payload->holder->alias->events[] = $label . '-holder'; + $payload->log[] = $label; +} + +function tracking_clear_invalidate_seed(string $kind): void +{ + $leaf = new TrackingClearInvalidateNode('seed'); + $leaf->child = new TrackingClearInvalidateChild('child'); + + $payload = new TrackingClearInvalidatePayload(); + $payload->objectRef =& $leaf; + $payload->arrayRef = [ + 'alias' => &$leaf, + 'child' => &$leaf->child, + ]; + $payload->holder->alias =& $leaf; + $payload->holder->child =& $leaf->child; + $payload->log[] = 'seed'; + + tracking_clear_invalidate_set($kind, $payload); + tracking_clear_invalidate_apply($payload, 'seed-node', 'seed-child', 'seed'); +} + +function tracking_clear_invalidate_dump(string $kind, string $label): void +{ + $payload = tracking_clear_invalidate_get($kind); + if (!$payload instanceof TrackingClearInvalidatePayload) { + echo $kind, '@', $label, '=missing', "\n"; + return; + } + + echo $kind, '@', $label, '=', + $payload->objectRef->name, ',', + count($payload->objectRef->events), ',', + $payload->objectRef->child->name, ',', + count($payload->objectRef->child->events), ',', + count($payload->log), ',', + ($payload->objectRef === $payload->arrayRef['alias'] ? 'same' : 'copy'), ',', + ($payload->objectRef->child === $payload->arrayRef['child'] ? 'same' : 'copy'), ',', + ($payload->holder->alias === $payload->objectRef ? 'same' : 'copy'), ',', + ($payload->holder->child === $payload->objectRef->child ? 'same' : 'copy'), ',', + ($payload->holder->alias->child === $payload->objectRef->child ? 'same' : 'copy'), + "\n"; +} + +$action = $_GET['action'] ?? 'read'; +$kind = $_GET['kind'] ?? 'class'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + tracking_clear_invalidate_seed($kind); + tracking_clear_invalidate_dump($kind, 'seed'); + return; +} + +if ($action === 'clear_after_access') { + $payload = tracking_clear_invalidate_get($kind); + if (!$payload instanceof TrackingClearInvalidatePayload) { + throw new RuntimeException('missing payload'); + } + + tracking_clear_invalidate_apply($payload, 'clear-node', 'clear-child', 'clear'); + tracking_clear_invalidate_dump($kind, 'clear-before'); + OPcache\volatile_clear(); + echo "clear\n"; + return; +} + +if ($action === 'reset_after_access') { + $payload = tracking_clear_invalidate_get($kind); + if (!$payload instanceof TrackingClearInvalidatePayload) { + throw new RuntimeException('missing payload'); + } + + tracking_clear_invalidate_apply($payload, 'reset-node', 'reset-child', 'reset'); + tracking_clear_invalidate_dump($kind, 'reset-before'); + opcache_reset(); + echo "reset-after\n"; + return; +} + +if ($action === 'invalidate_after_access') { + $payload = tracking_clear_invalidate_get($kind); + if (!$payload instanceof TrackingClearInvalidatePayload) { + throw new RuntimeException('missing payload'); + } + + tracking_clear_invalidate_apply($payload, 'invalidate-node', 'invalidate-child', 'invalidate'); + tracking_clear_invalidate_dump($kind, 'invalidate-before'); + var_dump(opcache_invalidate(__FILE__, true)); + return; +} + +if ($action === 'read') { + tracking_clear_invalidate_dump($kind, 'read'); + return; +} + +throw new RuntimeException('unknown action'); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_tracking_clear_invalidate_after_access_001.php'; +echo file_get_contents($base . '?action=reset'); +foreach (['class', 'property', 'method'] as $kind) { + echo file_get_contents($base . '?action=seed&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); + echo file_get_contents($base . '?action=clear_after_access&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); + echo file_get_contents($base . '?action=seed&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); + echo file_get_contents($base . '?action=reset_after_access&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); + echo file_get_contents($base . '?action=seed&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); + echo file_get_contents($base . '?action=invalidate_after_access&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +class@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +class@read=seed-node,2,seed-child,1,2,same,same,same,same,same +class@clear-before=clear-node,4,clear-child,2,3,same,same,same,same,same +clear +class@read=missing +class@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +class@read=seed-node,2,seed-child,1,2,same,same,same,same,same +class@reset-before=reset-node,4,reset-child,2,3,same,same,same,same,same +reset-after +class@read=missing +class@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +class@read=seed-node,2,seed-child,1,2,same,same,same,same,same +class@invalidate-before=invalidate-node,4,invalidate-child,2,3,same,same,same,same,same +bool(true) +class@read=missing +property@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +property@read=seed-node,2,seed-child,1,2,same,same,same,same,same +property@clear-before=clear-node,4,clear-child,2,3,same,same,same,same,same +clear +property@read=missing +property@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +property@read=seed-node,2,seed-child,1,2,same,same,same,same,same +property@reset-before=reset-node,4,reset-child,2,3,same,same,same,same,same +reset-after +property@read=missing +property@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +property@read=seed-node,2,seed-child,1,2,same,same,same,same,same +property@invalidate-before=invalidate-node,4,invalidate-child,2,3,same,same,same,same,same +bool(true) +property@read=missing +method@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +method@read=seed-node,2,seed-child,1,2,same,same,same,same,same +method@clear-before=clear-node,4,clear-child,2,3,same,same,same,same,same +clear +method@read=missing +method@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +method@read=seed-node,2,seed-child,1,2,same,same,same,same,same +method@reset-before=reset-node,4,reset-child,2,3,same,same,same,same,same +reset-after +method@read=missing +method@seed=seed-node,2,seed-child,1,2,same,same,same,same,same +method@read=seed-node,2,seed-child,1,2,same,same,same,same,same +method@invalidate-before=invalidate-node,4,invalidate-child,2,3,same,same,same,same,same +bool(true) +method@read=missing \ No newline at end of file diff --git a/ext/opcache/tests/static_cache_volatile_static_tracking_reference_object_graph_001.phpt b/ext/opcache/tests/static_cache_volatile_static_tracking_reference_object_graph_001.phpt new file mode 100644 index 000000000000..ad787004ce7a --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_tracking_reference_object_graph_001.phpt @@ -0,0 +1,230 @@ +--TEST-- +OPcache VolatileStatic tracking persists deep mutations through referenced object graphs +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +holder = new stdClass(); + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingReferenceClassState +{ + public static ?TrackingReferencePayload $payload = null; +} + +class TrackingReferencePropertyState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?TrackingReferencePayload $payload = null; +} + +class TrackingReferenceMethodState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function payload(?TrackingReferencePayload $payload = null): ?TrackingReferencePayload + { + static $value = null; + + if ($payload !== null) { + $value = $payload; + } + + return $value; + } +} + +function tracking_reference_set(string $kind, ?TrackingReferencePayload $payload): void +{ + switch ($kind) { + case 'class': + TrackingReferenceClassState::$payload = $payload; + return; + + case 'property': + TrackingReferencePropertyState::$payload = $payload; + return; + + case 'method': + TrackingReferenceMethodState::payload($payload); + return; + + default: + throw new RuntimeException('unknown kind'); + } +} + +function tracking_reference_get(string $kind): ?TrackingReferencePayload +{ + return match ($kind) { + 'class' => TrackingReferenceClassState::$payload, + 'property' => TrackingReferencePropertyState::$payload, + 'method' => TrackingReferenceMethodState::payload(), + default => throw new RuntimeException('unknown kind'), + }; +} + +function tracking_reference_seed(string $kind): void +{ + $leaf = new TrackingReferenceNode('seed'); + $leaf->child = new TrackingReferenceChild('child'); + + $payload = new TrackingReferencePayload(); + $payload->objectRef =& $leaf; + $payload->arrayRef = [ + 'alias' => &$leaf, + 'child' => &$leaf->child, + ]; + $payload->holder->alias =& $leaf; + $payload->holder->child =& $leaf->child; + $payload->log[] = 'seed'; + + tracking_reference_set($kind, $payload); + + $leaf->name = 'seed-updated'; + $leaf->events[] = 'source-object'; + $leaf->child->name = 'child-updated'; + $leaf->child->events[] = 'source-child'; + $payload->arrayRef['alias']->events[] = 'array-alias'; + $payload->holder->alias->child->events[] = 'holder-alias-child'; + $payload->log[] = 'seed-mutated'; +} + +function tracking_reference_mutate(string $kind): void +{ + $payload = tracking_reference_get($kind); + if (!$payload instanceof TrackingReferencePayload) { + throw new RuntimeException('missing payload'); + } + + $payload->objectRef->name = 'mutated-again'; + $payload->objectRef->events[] = 'object-ref'; + $payload->arrayRef['child']->name = 'child-mutated-again'; + $payload->arrayRef['child']->events[] = 'array-child'; + $payload->holder->alias->events[] = 'holder-alias'; + $payload->holder->alias->child->events[] = 'holder-alias-child'; + $payload->log[] = 'mutated'; +} + +function tracking_reference_dump(string $kind, string $label): void +{ + $payload = tracking_reference_get($kind); + if (!$payload instanceof TrackingReferencePayload) { + echo $kind, '@', $label, '=missing', "\n"; + return; + } + + echo $kind, '@', $label, '=', + $payload->objectRef->name, ',', + count($payload->objectRef->events), ',', + $payload->objectRef->child->name, ',', + count($payload->objectRef->child->events), ',', + count($payload->log), ',', + ($payload->objectRef === $payload->arrayRef['alias'] ? 'same' : 'copy'), ',', + ($payload->objectRef->child === $payload->arrayRef['child'] ? 'same' : 'copy'), ',', + ($payload->holder->alias === $payload->objectRef ? 'same' : 'copy'), ',', + ($payload->holder->child === $payload->objectRef->child ? 'same' : 'copy'), ',', + ($payload->holder->alias->child === $payload->objectRef->child ? 'same' : 'copy'), + "\n"; +} + +$action = $_GET['action'] ?? 'read'; +$kind = $_GET['kind'] ?? 'class'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + tracking_reference_seed($kind); + tracking_reference_dump($kind, 'seed'); + return; +} + +if ($action === 'mutate') { + tracking_reference_mutate($kind); + tracking_reference_dump($kind, 'mutate'); + return; +} + +if ($action === 'read') { + tracking_reference_dump($kind, 'read'); + return; +} + +throw new RuntimeException('unknown action'); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_tracking_reference_object_graph_001.php'; +echo file_get_contents($base . '?action=reset'); +foreach (['class', 'property', 'method'] as $kind) { + echo file_get_contents($base . '?action=seed&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); + echo file_get_contents($base . '?action=mutate&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +class@seed=seed-updated,2,child-updated,2,2,same,same,same,same,same +class@read=seed-updated,2,child-updated,2,2,same,same,same,same,same +class@mutate=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +class@read=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +property@seed=seed-updated,2,child-updated,2,2,same,same,same,same,same +property@read=seed-updated,2,child-updated,2,2,same,same,same,same,same +property@mutate=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +property@read=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +method@seed=seed-updated,2,child-updated,2,2,same,same,same,same,same +method@read=seed-updated,2,child-updated,2,2,same,same,same,same,same +method@mutate=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same +method@read=mutated-again,4,child-mutated-again,4,3,same,same,same,same,same diff --git a/ext/opcache/tests/static_cache_volatile_static_tracking_shared_dependency_001.phpt b/ext/opcache/tests/static_cache_volatile_static_tracking_shared_dependency_001.phpt new file mode 100644 index 000000000000..c33e853d44ff --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_tracking_shared_dependency_001.phpt @@ -0,0 +1,260 @@ +--TEST-- +OPcache VolatileStatic tracking marks all roots sharing a mutated dependency dirty +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + ['count' => 0, 'leaf' => $leaf], 'events' => []]; + $payload = new TrackingSharedPayload($leaf, $box); + $payload->refs['leaf'] = $leaf; + $payload->refs['box'] =& $payload->box; + + return $payload; +} + +function tracking_shared_pair(string $kind): array +{ + return match ($kind) { + 'class' => [TrackingSharedClassA::$payload, TrackingSharedClassB::$payload], + 'property' => [TrackingSharedPropertyA::$payload, TrackingSharedPropertyB::$payload], + 'method' => [TrackingSharedMethodA::payload(), TrackingSharedMethodB::payload()], + 'blob' => [TrackingSharedBlobState::$first, TrackingSharedBlobState::$second], + default => throw new RuntimeException('unknown kind'), + }; +} + +function tracking_shared_seed(string $kind): void +{ + $payload = tracking_shared_payload($kind); + + match ($kind) { + 'class' => [TrackingSharedClassA::$payload = $payload, TrackingSharedClassB::$payload = $payload], + 'property' => [TrackingSharedPropertyA::$payload = $payload, TrackingSharedPropertyB::$payload = $payload], + 'method' => [TrackingSharedMethodA::payload($payload), TrackingSharedMethodB::payload($payload)], + 'blob' => [TrackingSharedBlobState::$first = $payload, TrackingSharedBlobState::$second = $payload], + default => throw new RuntimeException('unknown kind'), + }; +} + +function tracking_shared_mutate(string $kind): void +{ + [$first, $second] = tracking_shared_pair($kind); + if (!$first instanceof TrackingSharedPayload || !$second instanceof TrackingSharedPayload) { + throw new RuntimeException('missing payload'); + } + + if ($kind !== 'blob' && $first !== $second) { + $second = $first; + match ($kind) { + 'class' => TrackingSharedClassB::$payload = $second, + 'property' => TrackingSharedPropertyB::$payload = $second, + 'method' => TrackingSharedMethodB::payload($second), + default => throw new RuntimeException('unknown kind'), + }; + } + + $first->leaf->events[] = $kind . '-leaf'; + $first->box['nested']['count']++; + $first->box['nested']['leaf']->events[] = $kind . '-nested-leaf'; + $first->refs['box']['events'][] = $kind . '-ref-array'; +} + +function tracking_shared_dump(string $kind): void +{ + [$first, $second] = tracking_shared_pair($kind); + echo $kind, '@pair=', ($first === $second ? 'same' : 'copy'), "\n"; + foreach ([$first, $second] as $index => $payload) { + if (!$payload instanceof TrackingSharedPayload) { + echo $kind, '#', $index, '=missing', "\n"; + continue; + } + + echo $kind, '#', $index, '=', + count($payload->leaf->events), ',', + $payload->box['nested']['count'], ',', + count($payload->box['nested']['leaf']->events), ',', + count($payload->box['events']), ',', + ($payload->refs['leaf'] === $payload->leaf ? 'same' : 'copy'), ',', + ($payload->refs['box'] === $payload->box ? 'same' : 'copy'), + "\n"; + } +} + +$action = $_GET['action'] ?? 'read'; +$kind = $_GET['kind'] ?? 'class'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + tracking_shared_seed($kind); + tracking_shared_dump($kind); + return; +} + +if ($action === 'mutate') { + tracking_shared_mutate($kind); + tracking_shared_dump($kind); + return; +} + +if ($action === 'read') { + tracking_shared_dump($kind); + return; +} + +throw new RuntimeException('unknown action'); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_tracking_shared_dependency_001.php'; +echo file_get_contents($base . '?action=reset'); +foreach (['class', 'property', 'method', 'blob'] as $kind) { + echo file_get_contents($base . '?action=seed&kind=' . $kind); + echo file_get_contents($base . '?action=mutate&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +class@pair=same +class#0=0,0,0,0,same,same +class#1=0,0,0,0,same,same +class@pair=same +class#0=2,1,2,1,same,same +class#1=2,1,2,1,same,same +class@pair=copy +class#0=2,1,2,1,same,same +class#1=2,1,2,1,same,same +property@pair=same +property#0=0,0,0,0,same,same +property#1=0,0,0,0,same,same +property@pair=same +property#0=2,1,2,1,same,same +property#1=2,1,2,1,same,same +property@pair=copy +property#0=2,1,2,1,same,same +property#1=2,1,2,1,same,same +method@pair=same +method#0=0,0,0,0,same,same +method#1=0,0,0,0,same,same +method@pair=same +method#0=2,1,2,1,same,same +method#1=2,1,2,1,same,same +method@pair=copy +method#0=2,1,2,1,same,same +method#1=2,1,2,1,same,same +blob@pair=same +blob#0=0,0,0,0,same,same +blob#1=0,0,0,0,same,same +blob@pair=same +blob#0=2,1,2,1,same,same +blob#1=2,1,2,1,same,same +blob@pair=same +blob#0=2,1,2,1,same,same +blob#1=2,1,2,1,same,same diff --git a/ext/opcache/tests/static_cache_volatile_static_tracking_shared_reference_cell_001.phpt b/ext/opcache/tests/static_cache_volatile_static_tracking_shared_reference_cell_001.phpt new file mode 100644 index 000000000000..a5af664ca906 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_tracking_shared_reference_cell_001.phpt @@ -0,0 +1,301 @@ +--TEST-- +OPcache VolatileStatic tracking publishes shared reference-cell mutations before restore and detaches roots after restore +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- +holder = new stdClass(); + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingSharedReferenceClassA +{ + public static ?TrackingSharedReferencePayload $payload = null; +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingSharedReferenceClassB +{ + public static ?TrackingSharedReferencePayload $payload = null; +} + +class TrackingSharedReferencePropertyA +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?TrackingSharedReferencePayload $payload = null; +} + +class TrackingSharedReferencePropertyB +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static ?TrackingSharedReferencePayload $payload = null; +} + +class TrackingSharedReferenceMethodA +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function payload(?TrackingSharedReferencePayload $payload = null): ?TrackingSharedReferencePayload + { + static $value = null; + + if ($payload !== null) { + $value = $payload; + } + + return $value; + } +} + +class TrackingSharedReferenceMethodB +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function payload(?TrackingSharedReferencePayload $payload = null): ?TrackingSharedReferencePayload + { + static $value = null; + + if ($payload !== null) { + $value = $payload; + } + + return $value; + } +} + +#[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] +class TrackingSharedReferenceBlob +{ + public static ?TrackingSharedReferencePayload $first = null; + public static ?TrackingSharedReferencePayload $second = null; +} + +function tracking_shared_reference_make_payload(string $label, TrackingSharedReferenceNode &$shared): TrackingSharedReferencePayload +{ + $payload = new TrackingSharedReferencePayload($label); + $payload->nodeRef =& $shared; + $payload->refs['node'] =& $shared; + $payload->refs['child'] =& $shared->child; + $payload->holder->node =& $shared; + $payload->holder->child =& $shared->child; + $payload->labels[] = $label; + + return $payload; +} + +function tracking_shared_reference_pair(string $kind): array +{ + return match ($kind) { + 'class' => [TrackingSharedReferenceClassA::$payload, TrackingSharedReferenceClassB::$payload], + 'property' => [TrackingSharedReferencePropertyA::$payload, TrackingSharedReferencePropertyB::$payload], + 'method' => [TrackingSharedReferenceMethodA::payload(), TrackingSharedReferenceMethodB::payload()], + 'blob' => [TrackingSharedReferenceBlob::$first, TrackingSharedReferenceBlob::$second], + default => throw new RuntimeException('unknown kind'), + }; +} + +function tracking_shared_reference_seed(string $kind): void +{ + $shared = new TrackingSharedReferenceNode('seed'); + $shared->child = new TrackingSharedReferenceChild('child'); + + $first = tracking_shared_reference_make_payload($kind . '-A', $shared); + $second = tracking_shared_reference_make_payload($kind . '-B', $shared); + + match ($kind) { + 'class' => [TrackingSharedReferenceClassA::$payload = $first, TrackingSharedReferenceClassB::$payload = $second], + 'property' => [TrackingSharedReferencePropertyA::$payload = $first, TrackingSharedReferencePropertyB::$payload = $second], + 'method' => [TrackingSharedReferenceMethodA::payload($first), TrackingSharedReferenceMethodB::payload($second)], + 'blob' => [TrackingSharedReferenceBlob::$first = $first, TrackingSharedReferenceBlob::$second = $second], + default => throw new RuntimeException('unknown kind'), + }; + + $first->nodeRef->name = 'seed-updated'; + $first->nodeRef->events[] = 'seed-node'; + $first->refs['child']->name = 'child-updated'; + $first->holder->child->events[] = 'seed-child'; + $first->labels[] = 'seed-mutated'; + $second->labels[] = 'seed-observed'; +} + +function tracking_shared_reference_mutate(string $kind): void +{ + [$first, $second] = tracking_shared_reference_pair($kind); + if (!$first instanceof TrackingSharedReferencePayload || !$second instanceof TrackingSharedReferencePayload) { + throw new RuntimeException('missing payload'); + } + + $first->holder->node->name = 'mutated-again'; + $first->holder->node->events[] = 'mutate-node'; + $first->nodeRef->child->name = 'child-mutated-again'; + $first->nodeRef->child->events[] = 'mutate-child'; + $first->labels[] = 'mutated-first'; +} + +function tracking_shared_reference_dump_payload(string $kind, int $index, ?TrackingSharedReferencePayload $payload): void +{ + if (!$payload instanceof TrackingSharedReferencePayload) { + echo $kind, '#', $index, '=missing', "\n"; + return; + } + + echo $kind, '#', $index, '=', + $payload->label, ',', + $payload->nodeRef->name, ',', + count($payload->nodeRef->events), ',', + $payload->nodeRef->child->name, ',', + count($payload->nodeRef->child->events), ',', + count($payload->labels), ',', + ($payload->nodeRef === $payload->refs['node'] ? 'same' : 'copy'), ',', + ($payload->nodeRef === $payload->holder->node ? 'same' : 'copy'), ',', + ($payload->nodeRef->child === $payload->refs['child'] ? 'same' : 'copy'), ',', + ($payload->nodeRef->child === $payload->holder->child ? 'same' : 'copy'), + "\n"; +} + +function tracking_shared_reference_dump(string $kind): void +{ + [$first, $second] = tracking_shared_reference_pair($kind); + echo $kind, '@cross=', + ($first instanceof TrackingSharedReferencePayload && $second instanceof TrackingSharedReferencePayload && $first->nodeRef === $second->nodeRef ? 'same' : 'copy'), ',', + ($first instanceof TrackingSharedReferencePayload && $second instanceof TrackingSharedReferencePayload && $first->nodeRef->child === $second->nodeRef->child ? 'same' : 'copy'), + "\n"; + tracking_shared_reference_dump_payload($kind, 0, $first); + tracking_shared_reference_dump_payload($kind, 1, $second); +} + +$action = $_GET['action'] ?? 'read'; +$kind = $_GET['kind'] ?? 'class'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + tracking_shared_reference_seed($kind); + tracking_shared_reference_dump($kind); + return; +} + +if ($action === 'mutate') { + tracking_shared_reference_mutate($kind); + tracking_shared_reference_dump($kind); + return; +} + +if ($action === 'read') { + tracking_shared_reference_dump($kind); + return; +} + +throw new RuntimeException('unknown action'); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_tracking_shared_reference_cell_001.php'; +echo file_get_contents($base . '?action=reset'); +foreach (['class', 'property', 'method', 'blob'] as $kind) { + echo file_get_contents($base . '?action=seed&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); + echo file_get_contents($base . '?action=mutate&kind=' . $kind); + echo file_get_contents($base . '?action=read&kind=' . $kind); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +class@cross=same,same +class#0=class-A,seed-updated,1,child-updated,1,2,same,same,same,same +class#1=class-B,seed-updated,1,child-updated,1,2,same,same,same,same +class@cross=copy,copy +class#0=class-A,seed-updated,1,child-updated,1,2,same,same,same,same +class#1=class-B,seed-updated,1,child-updated,1,2,same,same,same,same +class@cross=copy,copy +class#0=class-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +class#1=class-B,seed-updated,1,child-updated,1,2,same,same,same,same +class@cross=copy,copy +class#0=class-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +class#1=class-B,seed-updated,1,child-updated,1,2,same,same,same,same +property@cross=same,same +property#0=property-A,seed-updated,1,child-updated,1,2,same,same,same,same +property#1=property-B,seed-updated,1,child-updated,1,2,same,same,same,same +property@cross=copy,copy +property#0=property-A,seed-updated,1,child-updated,1,2,same,same,same,same +property#1=property-B,seed-updated,1,child-updated,1,2,same,same,same,same +property@cross=copy,copy +property#0=property-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +property#1=property-B,seed-updated,1,child-updated,1,2,same,same,same,same +property@cross=copy,copy +property#0=property-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +property#1=property-B,seed-updated,1,child-updated,1,2,same,same,same,same +method@cross=same,same +method#0=method-A,seed-updated,1,child-updated,1,2,same,same,same,same +method#1=method-B,seed-updated,1,child-updated,1,2,same,same,same,same +method@cross=copy,copy +method#0=method-A,seed-updated,1,child-updated,1,2,same,same,same,same +method#1=method-B,seed-updated,1,child-updated,1,2,same,same,same,same +method@cross=copy,copy +method#0=method-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +method#1=method-B,seed-updated,1,child-updated,1,2,same,same,same,same +method@cross=copy,copy +method#0=method-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +method#1=method-B,seed-updated,1,child-updated,1,2,same,same,same,same +blob@cross=same,same +blob#0=blob-A,seed-updated,1,child-updated,1,2,same,same,same,same +blob#1=blob-B,seed-updated,1,child-updated,1,2,same,same,same,same +blob@cross=copy,copy +blob#0=blob-A,seed-updated,1,child-updated,1,2,same,same,same,same +blob#1=blob-B,seed-updated,1,child-updated,1,2,same,same,same,same +blob@cross=copy,copy +blob#0=blob-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +blob#1=blob-B,seed-updated,1,child-updated,1,2,same,same,same,same +blob@cross=copy,copy +blob#0=blob-A,mutated-again,2,child-mutated-again,2,3,same,same,same,same +blob#1=blob-B,seed-updated,1,child-updated,1,2,same,same,same,same diff --git a/ext/opcache/tests/static_cache_volatile_static_ttl_001.phpt b/ext/opcache/tests/static_cache_volatile_static_ttl_001.phpt new file mode 100644 index 000000000000..29a10721f6a1 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_ttl_001.phpt @@ -0,0 +1,123 @@ +--TEST-- +OPcache VolatileStatic TTL expires class, property, and method state +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +reset +first=1,1,1,1,1,1,1,1 +second=2,2,2,2,2,2,2,2 +expired=1,1,1,1,1,1,1,1 +again=2,2,2,2,2,2,2,2 diff --git a/ext/opcache/tests/static_cache_volatile_static_ttl_invalid_attribute_001.phpt b/ext/opcache/tests/static_cache_volatile_static_ttl_invalid_attribute_001.phpt new file mode 100644 index 000000000000..6a26daf7b324 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_ttl_invalid_attribute_001.phpt @@ -0,0 +1,19 @@ +--TEST-- +OPcache VolatileStatic rejects negative TTL attributes +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + +--EXPECTF-- +Fatal error: OPcache\VolatileStatic ttl must be greater than or equal to 0 in %s on line %d diff --git a/ext/opcache/tests/static_cache_volatile_static_ttl_refresh_001.phpt b/ext/opcache/tests/static_cache_volatile_static_ttl_refresh_001.phpt new file mode 100644 index 000000000000..374ed041742e --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_ttl_refresh_001.phpt @@ -0,0 +1,68 @@ +--TEST-- +OPcache VolatileStatic TTL is refreshed by publication +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + +--CLEAN-- + +--EXPECT-- +reset +first=1,1 +refreshed=2,2 +still-live=3,3 diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 465b15cd9576..efa7e93ed77d 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -20,7 +20,10 @@ #include #include "php.h" +#include "php_ini.h" + #include "ZendAccelerator.h" +#include "Zend/zend_attributes.h" #include "zend_API.h" #include "zend_closures.h" #include "zend_extensions.h" @@ -28,12 +31,13 @@ #include "zend_shared_alloc.h" #include "zend_accelerator_blacklist.h" #include "zend_file_cache.h" -#include "php_ini.h" -#include "SAPI.h" #include "zend_virtual_cwd.h" +#include "zend_static_cache.h" + +#include "SAPI.h" + #include "ext/standard/info.h" #include "ext/date/php_date.h" -#include "opcache_arginfo.h" #ifdef HAVE_JIT #include "jit/zend_jit.h" @@ -59,7 +63,7 @@ static zif_handler orig_file_exists = NULL; static zif_handler orig_is_file = NULL; static zif_handler orig_is_readable = NULL; -static bool validate_api_restriction(void) +bool zend_opcache_validate_api_restriction(void) { if (ZCG(accel_directives).restrict_api && *ZCG(accel_directives).restrict_api) { size_t len = strlen(ZCG(accel_directives).restrict_api); @@ -74,6 +78,80 @@ static bool validate_api_restriction(void) return true; } +static ZEND_INI_MH(OnUpdateStaticCacheVolatileSizeMb) +{ + zend_long *p, memsize; + + if (accel_startup_ok) { + if (strcmp(sapi_module.name, "fpm-fcgi") == 0) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.volatile_size_mb cannot be changed when OPcache is already set up. Are you using php_admin_value[opcache.static_cache.volatile_size_mb] in an individual pool's configuration?\n"); + } else { + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.volatile_size_mb cannot be changed when OPcache is already set up.\n"); + } + return FAILURE; + } + + p = ZEND_INI_GET_ADDR(); + memsize = atoi(ZSTR_VAL(new_value)); + + /* zero disables the volatile cache */ + if (memsize == 0) { + *p = 0; + return SUCCESS; + } + + /* sanity check we must use at least 8 MB */ + if (memsize < 8) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.volatile_size_mb is set below the required 8MB.\n"); + return FAILURE; + } + + if (UNEXPECTED(memsize > ZEND_LONG_MAX / (1024 * 1024))) { + *p = ZEND_LONG_MAX & ~(1024 * 1024 - 1); + } else { + *p = memsize * (1024 * 1024); + } + + return SUCCESS; +} + +static ZEND_INI_MH(OnUpdateStaticCachePersistentSizeMb) +{ + zend_long *p, memsize; + + if (accel_startup_ok) { + if (strcmp(sapi_module.name, "fpm-fcgi") == 0) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.persistent_size_mb cannot be changed when OPcache is already set up. Are you using php_admin_value[opcache.static_cache.persistent_size_mb] in an individual pool's configuration?\n"); + } else { + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.persistent_size_mb cannot be changed when OPcache is already set up.\n"); + } + return FAILURE; + } + + p = ZEND_INI_GET_ADDR(); + memsize = atoi(ZSTR_VAL(new_value)); + + /* zero disables the persistent cache */ + if (memsize == 0) { + *p = 0; + return SUCCESS; + } + + /* sanity check we must use at least 8 MB */ + if (memsize < 8) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.persistent_size_mb is set below the required 8MB.\n"); + return FAILURE; + } + + if (UNEXPECTED(memsize > ZEND_LONG_MAX / (1024 * 1024))) { + *p = ZEND_LONG_MAX & ~(1024 * 1024 - 1); + } else { + *p = memsize * (1024 * 1024); + } + + return SUCCESS; +} + static ZEND_INI_MH(OnUpdateMemoryConsumption) { if (accel_startup_ok) { @@ -292,6 +370,8 @@ ZEND_INI_BEGIN() STD_PHP_INI_ENTRY("opcache.log_verbosity_level" , "1" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.log_verbosity_level, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.memory_consumption" , "128" , PHP_INI_SYSTEM, OnUpdateMemoryConsumption, accel_directives.memory_consumption, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.static_cache.volatile_size_mb", "0", PHP_INI_SYSTEM, OnUpdateStaticCacheVolatileSizeMb, accel_directives.static_cache_volatile_size_mb, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.static_cache.persistent_size_mb", "0", PHP_INI_SYSTEM, OnUpdateStaticCachePersistentSizeMb, accel_directives.static_cache_persistent_size_mb, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.interned_strings_buffer", "8" , PHP_INI_SYSTEM, OnUpdateInternedStringsBuffer, accel_directives.interned_strings_buffer, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.max_accelerated_files" , "10000", PHP_INI_SYSTEM, OnUpdateMaxAcceleratedFiles, accel_directives.max_accelerated_files, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.max_wasted_percentage" , "5" , PHP_INI_SYSTEM, OnUpdateMaxWastedPercentage, accel_directives.max_wasted_percentage, zend_accel_globals, accel_globals) @@ -443,11 +523,21 @@ static ZEND_NAMED_FUNCTION(accel_is_readable) static ZEND_MINIT_FUNCTION(zend_accelerator) { + if (zend_opcache_register_functions(type) == FAILURE) { + return FAILURE; + } + start_accel_extension(); + zend_opcache_static_cache_minit(); return SUCCESS; } +static ZEND_RSHUTDOWN_FUNCTION(zend_accelerator) +{ + return zend_opcache_static_cache_rshutdown(); +} + void zend_accel_register_ini_entries(void) { zend_module_entry *module = zend_hash_str_find_ptr_lc(&module_registry, @@ -484,6 +574,7 @@ static ZEND_MSHUTDOWN_FUNCTION(zend_accelerator) { (void)type; /* keep the compiler happy */ + zend_opcache_static_cache_mshutdown(); UNREGISTER_INI_ENTRIES(); accel_shutdown(); @@ -499,6 +590,28 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS) } else { php_info_print_table_row(2, "Opcode Caching", "Disabled"); } + + if (!zend_opcache_static_cache_volatile_is_enabled()) { + php_info_print_table_row(2, "Volatile Static Cache", "Disabled"); + } else if (zend_opcache_static_cache_volatile_is_available()) { + php_info_print_table_row(2, "Volatile Static Cache", "Enabled"); + } else { + php_info_print_table_row(2, "Volatile Static Cache", "Unavailable"); + if (zend_opcache_static_cache_volatile_failure_reason()) { + php_info_print_table_row(2, "Volatile Static Cache Failure", zend_opcache_static_cache_volatile_failure_reason()); + } + } + if (!zend_opcache_static_cache_persistent_is_enabled()) { + php_info_print_table_row(2, "Persistent Static Cache", "Disabled"); + } else if (zend_opcache_static_cache_persistent_is_available()) { + php_info_print_table_row(2, "Persistent Static Cache", "Enabled"); + } else { + php_info_print_table_row(2, "Persistent Static Cache", "Unavailable"); + if (zend_opcache_static_cache_persistent_failure_reason()) { + php_info_print_table_row(2, "Persistent Static Cache Failure", zend_opcache_static_cache_persistent_failure_reason()); + } + } + if (ZCG(enabled) && accel_startup_ok && ZCG(accel_directives).optimization_level) { php_info_print_table_row(2, "Optimization", "Enabled"); } else { @@ -602,11 +715,11 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS) zend_module_entry opcache_module_entry = { STANDARD_MODULE_HEADER, ACCELERATOR_PRODUCT_NAME, - ext_functions, + NULL, ZEND_MINIT(zend_accelerator), ZEND_MSHUTDOWN(zend_accelerator), ZEND_RINIT(zend_accelerator), - NULL, + ZEND_RSHUTDOWN(zend_accelerator), zend_accel_info, PHP_VERSION, NO_MODULE_GLOBALS, @@ -665,14 +778,14 @@ static int accelerator_get_scripts(zval *return_value) ZEND_FUNCTION(opcache_get_status) { zend_long reqs; - zval memory_usage, statistics, scripts; + zval memory_usage, statistics, scripts, volatile_cache, persistent_cache; bool fetch_scripts = true; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &fetch_scripts) == FAILURE) { RETURN_THROWS(); } - if (!validate_api_restriction()) { + if (!zend_opcache_validate_api_restriction()) { RETURN_FALSE; } @@ -688,6 +801,13 @@ ZEND_FUNCTION(opcache_get_status) if (ZCG(accel_directives).file_cache) { add_assoc_string(return_value, "file_cache", ZCG(accel_directives).file_cache); } + + /* Static cache */ + zend_opcache_static_cache_volatile_get_status(&volatile_cache); + add_assoc_zval(return_value, "volatile_cache", &volatile_cache); + zend_opcache_static_cache_persistent_get_status(&persistent_cache); + add_assoc_zval(return_value, "persistent_cache", &persistent_cache); + if (file_cache_only) { add_assoc_bool(return_value, "file_cache_only", 1); return; @@ -801,7 +921,7 @@ ZEND_FUNCTION(opcache_get_configuration) ZEND_PARSE_PARAMETERS_NONE(); - if (!validate_api_restriction()) { + if (!zend_opcache_validate_api_restriction()) { RETURN_FALSE; } @@ -822,6 +942,8 @@ ZEND_FUNCTION(opcache_get_configuration) add_assoc_long(&directives, "opcache.log_verbosity_level", ZCG(accel_directives).log_verbosity_level); add_assoc_long(&directives, "opcache.memory_consumption", ZCG(accel_directives).memory_consumption); + add_assoc_long(&directives, "opcache.static_cache.volatile_size_mb", ZCG(accel_directives).static_cache_volatile_size_mb); + add_assoc_long(&directives, "opcache.static_cache.persistent_size_mb", ZCG(accel_directives).static_cache_persistent_size_mb); add_assoc_long(&directives, "opcache.interned_strings_buffer",ZCG(accel_directives).interned_strings_buffer); add_assoc_long(&directives, "opcache.max_accelerated_files", ZCG(accel_directives).max_accelerated_files); add_assoc_double(&directives, "opcache.max_wasted_percentage", ZCG(accel_directives).max_wasted_percentage); @@ -906,7 +1028,7 @@ ZEND_FUNCTION(opcache_reset) { ZEND_PARSE_PARAMETERS_NONE(); - if (!validate_api_restriction()) { + if (!zend_opcache_validate_api_restriction()) { RETURN_FALSE; } @@ -918,6 +1040,8 @@ ZEND_FUNCTION(opcache_reset) RETURN_FALSE; } + zend_opcache_static_cache_invalidate_all(); + /* exclusive lock */ zend_shared_alloc_lock(); zend_accel_schedule_restart(ACCEL_RESTART_USER); @@ -935,7 +1059,7 @@ ZEND_FUNCTION(opcache_invalidate) RETURN_THROWS(); } - if (!validate_api_restriction()) { + if (!zend_opcache_validate_api_restriction()) { RETURN_FALSE; } @@ -1017,7 +1141,7 @@ ZEND_FUNCTION(opcache_is_script_cached) Z_PARAM_STR(script_name) ZEND_PARSE_PARAMETERS_END(); - if (!validate_api_restriction()) { + if (!zend_opcache_validate_api_restriction()) { RETURN_FALSE; } @@ -1037,7 +1161,7 @@ ZEND_FUNCTION(opcache_is_script_cached_in_file_cache) Z_PARAM_STR(script_name) ZEND_PARSE_PARAMETERS_END(); - if (!validate_api_restriction()) { + if (!zend_opcache_validate_api_restriction()) { RETURN_FALSE; } diff --git a/ext/opcache/zend_accelerator_module.h b/ext/opcache/zend_accelerator_module.h index cd46e07d1d27..e6c062dd138d 100644 --- a/ext/opcache/zend_accelerator_module.h +++ b/ext/opcache/zend_accelerator_module.h @@ -29,4 +29,6 @@ void zend_accel_register_ini_entries(void); void zend_accel_override_file_functions(void); +bool zend_opcache_validate_api_restriction(void); + #endif /* _ZEND_ACCELERATOR_MODULE_H */ diff --git a/ext/opcache/zend_opcache_serializer.h b/ext/opcache/zend_opcache_serializer.h new file mode 100644 index 000000000000..d7467b0b37d6 --- /dev/null +++ b/ext/opcache/zend_opcache_serializer.h @@ -0,0 +1,2044 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_OPCACHE_SERIALIZER_H +#define ZEND_OPCACHE_SERIALIZER_H + +#include + +#include "Zend/zend_attributes.h" +#include "Zend/zend_closures.h" + +#define ZEND_OPCACHE_SERIALIZER_ALIGN8(size) (((size) + 7UL) & ~7UL) + +#if defined(_MSC_VER) +# define ZEND_OPCACHE_SERIALIZER_PREFETCH_R(ptr) ((void) 0) +#else +# define ZEND_OPCACHE_SERIALIZER_PREFETCH_R(ptr) __builtin_prefetch((ptr), 0, 3) +#endif + +#define ZEND_OPCACHE_SERIALIZER_MAX_DEPTH 128 + +#define ZEND_OPCACHE_SERIALIZER_TYPE_UNDEF 0 +#define ZEND_OPCACHE_SERIALIZER_TYPE_NULL 1 +#define ZEND_OPCACHE_SERIALIZER_TYPE_FALSE 2 +#define ZEND_OPCACHE_SERIALIZER_TYPE_TRUE 3 +#define ZEND_OPCACHE_SERIALIZER_TYPE_LONG 4 +#define ZEND_OPCACHE_SERIALIZER_TYPE_DOUBLE 5 +#define ZEND_OPCACHE_SERIALIZER_TYPE_STRING 6 +#define ZEND_OPCACHE_SERIALIZER_TYPE_ARRAY 7 +#define ZEND_OPCACHE_SERIALIZER_TYPE_OBJECT 8 + +#define ZEND_OPCACHE_SERIALIZER_ARRAY_MAP 0x00 +#define ZEND_OPCACHE_SERIALIZER_ARRAY_PACKED 0x01 + +#define ZEND_OPCACHE_STATIC_CACHE_DIRECT_CACHE_SAFE_ATTRIBUTE "opcache\\__directcachesafe" + +#define ZEND_OPCACHE_SERIALIZER_OBJ_SERIALIZE 0x01 +#define ZEND_OPCACHE_SERIALIZER_OBJ_PROPS 0x02 +#define ZEND_OPCACHE_SERIALIZER_OBJ_PHP_SER 0x04 +#define ZEND_OPCACHE_SERIALIZER_OBJ_SAFE_DIRECT 0x08 + +typedef struct _zend_opcache_serializer_hdr_t { + uint8_t type; + uint8_t flags; + uint16_t _pad; + uint32_t data_size; +} zend_opcache_serializer_hdr_t; + +typedef struct _zend_opcache_serializer_wbuf_t { + unsigned char *data; + size_t len; + size_t cap; + uint32_t depth; + bool failed_unstorable; + HashTable visited_arrays; + HashTable seen_objects; +} zend_opcache_serializer_wbuf_t; + +typedef void (*zend_opcache_serializer_capture_func_t)(zval *value); + +typedef struct _zend_opcache_serializer_rbuf_t { + const unsigned char *data; + zend_opcache_serializer_capture_func_t capture_decoded_value; + zend_opcache_serializer_capture_func_t capture_decoded_reachable_value; + size_t len; + size_t pos; + uint32_t depth; + uint32_t capture_suppression; + bool skip_decoded_value_capture; +} zend_opcache_serializer_rbuf_t; + +static zend_always_inline void zend_opcache_serializer_capture_decoded_value(zend_opcache_serializer_rbuf_t *rb, zval *value) +{ + if (rb->capture_decoded_value != NULL) { + rb->capture_decoded_value(value); + } +} + +static zend_always_inline void zend_opcache_serializer_capture_decoded_reachable_value(zend_opcache_serializer_rbuf_t *rb, zval *value) +{ + if (rb->capture_decoded_reachable_value != NULL) { + rb->capture_decoded_reachable_value(value); + } +} + +static zend_always_inline bool zend_opcache_serializer_try_u32(size_t value, uint32_t *out) +{ + if (value > UINT32_MAX) { + return false; + } + + *out = (uint32_t) value; + + return true; +} + +static zend_always_inline void zend_opcache_serializer_wbuf_init(zend_opcache_serializer_wbuf_t *wb, size_t initial_cap) +{ + wb->data = emalloc(initial_cap); + wb->len = 0; + wb->cap = initial_cap; + wb->depth = 0; + wb->failed_unstorable = false; + + zend_hash_init(&wb->visited_arrays, 8, NULL, NULL, 0); + zend_hash_init(&wb->seen_objects, 8, NULL, NULL, 0); +} + +static zend_always_inline void zend_opcache_serializer_wbuf_destroy(zend_opcache_serializer_wbuf_t *wb) +{ + zend_hash_destroy(&wb->visited_arrays); + zend_hash_destroy(&wb->seen_objects); +} + +static zend_always_inline void zend_opcache_serializer_wbuf_grow(zend_opcache_serializer_wbuf_t *wb, size_t need) +{ + size_t new_cap; + + if (wb->len + need <= wb->cap) { + return; + } + + new_cap = wb->cap; + if (new_cap == 0) { + new_cap = need; + } + + while (new_cap < wb->len + need) { + new_cap = new_cap + (new_cap >> 1); + } + + wb->data = erealloc(wb->data, new_cap); + wb->cap = new_cap; +} + +static zend_always_inline void *zend_opcache_serializer_wbuf_reserve(zend_opcache_serializer_wbuf_t *wb, size_t size) +{ + size_t aligned_size; + void *ptr; + + aligned_size = ZEND_OPCACHE_SERIALIZER_ALIGN8(size); + zend_opcache_serializer_wbuf_grow(wb, aligned_size); + ptr = wb->data + wb->len; + wb->len += aligned_size; + + return ptr; +} + +static zend_always_inline void zend_opcache_serializer_wbuf_zero_padding(void *ptr, size_t size) +{ + size_t aligned_size = ZEND_OPCACHE_SERIALIZER_ALIGN8(size); + + if (aligned_size > size) { + memset((unsigned char *) ptr + size, 0, aligned_size - size); + } +} + +static zend_always_inline void zend_opcache_serializer_wbuf_write(zend_opcache_serializer_wbuf_t *wb, const void *src, size_t size) +{ + void *dst; + + dst = zend_opcache_serializer_wbuf_reserve(wb, size); + memcpy(dst, src, size); + zend_opcache_serializer_wbuf_zero_padding(dst, size); +} + +static zend_always_inline zend_opcache_serializer_hdr_t *zend_opcache_serializer_wbuf_write_hdr( + zend_opcache_serializer_wbuf_t *wb, uint8_t type, uint8_t flags, uint32_t data_size) +{ + zend_opcache_serializer_hdr_t *hdr; + + hdr = (zend_opcache_serializer_hdr_t *) zend_opcache_serializer_wbuf_reserve(wb, sizeof(zend_opcache_serializer_hdr_t)); + hdr->type = type; + hdr->flags = flags; + hdr->_pad = 0; + hdr->data_size = data_size; + + return hdr; +} + +static zend_always_inline bool zend_opcache_serializer_patch_hdr(zend_opcache_serializer_wbuf_t *wb, size_t hdr_offset) +{ + zend_opcache_serializer_hdr_t *hdr; + uint32_t data_size; + size_t payload_size; + + if (wb->len < hdr_offset + ZEND_OPCACHE_SERIALIZER_ALIGN8(sizeof(zend_opcache_serializer_hdr_t))) { + return false; + } + + payload_size = wb->len - hdr_offset - ZEND_OPCACHE_SERIALIZER_ALIGN8(sizeof(zend_opcache_serializer_hdr_t)); + if (!zend_opcache_serializer_try_u32(payload_size, &data_size)) { + return false; + } + + hdr = (zend_opcache_serializer_hdr_t *) (wb->data + hdr_offset); + hdr->data_size = data_size; + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_write_bytes_entry(zend_opcache_serializer_wbuf_t *wb, + const char *bytes, uint32_t byte_len) +{ + uint32_t reserved = 0; + size_t entry_size; + unsigned char *dst; + + entry_size = sizeof(uint32_t) + sizeof(uint32_t) + (size_t) byte_len + 1; + if (entry_size > UINT32_MAX) { + return false; + } + + dst = (unsigned char *) zend_opcache_serializer_wbuf_reserve(wb, entry_size); + memcpy(dst, &byte_len, sizeof(uint32_t)); + memcpy(dst + sizeof(uint32_t), &reserved, sizeof(uint32_t)); + memcpy(dst + sizeof(uint32_t) + sizeof(uint32_t), bytes, byte_len); + dst[sizeof(uint32_t) + sizeof(uint32_t) + byte_len] = '\0'; + zend_opcache_serializer_wbuf_zero_padding(dst, entry_size); + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_encode_string(zend_opcache_serializer_wbuf_t *wb, + const zend_string *str) +{ + uint32_t string_len; + size_t payload_size; + + if (!zend_opcache_serializer_try_u32(ZSTR_LEN(str), &string_len)) { + return false; + } + + payload_size = sizeof(uint32_t) + sizeof(uint32_t) + (size_t) string_len + 1; + if (payload_size > UINT32_MAX) { + return false; + } + + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_STRING, 0, (uint32_t) payload_size); + + return zend_opcache_serializer_write_bytes_entry(wb, ZSTR_VAL(str), string_len); +} + +static zend_always_inline bool zend_opcache_serializer_write_string_key_entry(zend_opcache_serializer_wbuf_t *wb, + const char *key_str, uint32_t key_len) +{ + uint32_t is_string = 1; + size_t entry_size; + unsigned char *dst; + + entry_size = 2 * sizeof(uint32_t) + (size_t) key_len + 1; + if (entry_size > UINT32_MAX) { + return false; + } + + dst = (unsigned char *) zend_opcache_serializer_wbuf_reserve(wb, entry_size); + memcpy(dst, &is_string, sizeof(uint32_t)); + memcpy(dst + sizeof(uint32_t), &key_len, sizeof(uint32_t)); + memcpy(dst + 2 * sizeof(uint32_t), key_str, key_len); + dst[2 * sizeof(uint32_t) + key_len] = '\0'; + zend_opcache_serializer_wbuf_zero_padding(dst, entry_size); + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_enter_array(zend_opcache_serializer_wbuf_t *wb, const HashTable *ht) +{ + zend_ulong key; + zval tmp; + + key = (zend_ulong) (uintptr_t) ht; + if (zend_hash_index_exists(&wb->visited_arrays, key)) { + return false; + } + + ZVAL_NULL(&tmp); + zend_hash_index_add_new(&wb->visited_arrays, key, &tmp); + + return true; +} + +static zend_always_inline void zend_opcache_serializer_leave_array(zend_opcache_serializer_wbuf_t *wb, const HashTable *ht) +{ + zend_hash_index_del(&wb->visited_arrays, (zend_ulong) (uintptr_t) ht); +} + +static zend_always_inline bool zend_opcache_serializer_mark_object(zend_opcache_serializer_wbuf_t *wb, const zval *zv) +{ + zend_ulong key; + zval tmp; + + key = (zend_ulong) (uintptr_t) Z_OBJ_P(zv); + if (zend_hash_index_exists(&wb->seen_objects, key)) { + return false; + } + + ZVAL_NULL(&tmp); + zend_hash_index_add_new(&wb->seen_objects, key, &tmp); + + return true; +} + +static zend_always_inline size_t zend_opcache_serializer_sleep_property_count(HashTable *sleep_ht) +{ + zval *sleep_entry; + size_t count = 0; + + ZEND_HASH_FOREACH_VAL(sleep_ht, sleep_entry) { + if (Z_TYPE_P(sleep_entry) == IS_STRING) { + count++; + } + } ZEND_HASH_FOREACH_END(); + + return count; +} + +static zend_always_inline zval *zend_opcache_serializer_find_sleep_property(HashTable *props, + zend_class_entry *ce, zend_string *sleep_name, zend_string **resolved_name_out, + bool *resolved_name_owned_out) +{ + zend_property_info *prop_info; + zend_string *mangled_name; + zval *found_val; + + found_val = zend_hash_find(props, sleep_name); + if (found_val != NULL) { + *resolved_name_out = sleep_name; + *resolved_name_owned_out = false; + + return found_val; + } + + prop_info = zend_hash_find_ptr(&ce->properties_info, sleep_name); + if (prop_info != NULL) { + if ((prop_info->flags & ZEND_ACC_PRIVATE) != 0) { + mangled_name = zend_mangle_property_name(ZSTR_VAL(prop_info->ce->name), + ZSTR_LEN(prop_info->ce->name), ZSTR_VAL(sleep_name), ZSTR_LEN(sleep_name), 0) + ; + } else if ((prop_info->flags & ZEND_ACC_PROTECTED) != 0) { + mangled_name = zend_mangle_property_name("*", 1, + ZSTR_VAL(sleep_name), ZSTR_LEN(sleep_name), 0) + ; + } else { + mangled_name = NULL; + } + + if (mangled_name != NULL) { + found_val = zend_hash_find(props, mangled_name); + if (found_val != NULL) { + *resolved_name_out = mangled_name; + *resolved_name_owned_out = true; + + return found_val; + } + + zend_string_release(mangled_name); + } + } + + mangled_name = zend_mangle_property_name(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), + ZSTR_VAL(sleep_name), ZSTR_LEN(sleep_name), 0) + ; + + found_val = zend_hash_find(props, mangled_name); + if (found_val != NULL) { + *resolved_name_out = mangled_name; + *resolved_name_owned_out = true; + + return found_val; + } + + zend_string_release(mangled_name); + + mangled_name = zend_mangle_property_name("*", 1, + ZSTR_VAL(sleep_name), ZSTR_LEN(sleep_name), 0) + ; + + found_val = zend_hash_find(props, mangled_name); + if (found_val != NULL) { + *resolved_name_out = mangled_name; + *resolved_name_owned_out = true; + + return found_val; + } + + zend_string_release(mangled_name); + + *resolved_name_out = sleep_name; + *resolved_name_owned_out = false; + + return NULL; +} + +static zend_always_inline bool zend_opcache_serializer_safe_direct_cache_has_attribute(const HashTable *attributes) +{ + return attributes != NULL && + zend_get_attribute_str(attributes, ZEND_STRL(ZEND_OPCACHE_STATIC_CACHE_DIRECT_CACHE_SAFE_ATTRIBUTE)) != NULL + ; +} + +static zend_always_inline zend_class_entry *zend_opcache_serializer_find_safe_direct_cache_base(zend_class_entry *ce) +{ + zend_class_entry *base_ce = NULL; + + if (zend_opcache_static_cache_safe_direct_copy_func(ce, &base_ce) == NULL) { + return NULL; + } + + return base_ce; +} + +static zend_always_inline bool zend_opcache_serializer_class_magic_method_changed(zend_class_entry *ce, + zend_class_entry *base_ce, const char *name, size_t name_len) +{ + zend_function *func = zend_hash_str_find_ptr(&ce->function_table, name, name_len); + zend_function *base_func = zend_hash_str_find_ptr(&base_ce->function_table, name, name_len); + + if (func == NULL) { + return base_func != NULL; + } + + if (func == base_func || func->common.scope == base_ce) { + return false; + } + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_safe_direct_cache_allows_custom_serializers( + zend_class_entry *base_ce) +{ + return zend_opcache_static_cache_safe_direct_allows_custom_serializers(base_ce); +} + +static zend_always_inline bool zend_opcache_serializer_has_safe_direct_cache_overrides(zend_class_entry *ce, + zend_class_entry *base_ce) +{ + if (ce == base_ce) { + return false; + } + + if ((ce->__serialize != base_ce->__serialize || ce->__unserialize != base_ce->__unserialize) && + !zend_opcache_serializer_safe_direct_cache_allows_custom_serializers(base_ce) + ) { + return true; + } + + return zend_opcache_serializer_class_magic_method_changed(ce, base_ce, ZEND_STRL("__sleep")) || + zend_opcache_serializer_class_magic_method_changed(ce, base_ce, ZEND_STRL("__wakeup")) + ; +} + +static bool zend_opcache_serializer_value_has_unstorable_ex( + const zval *value, + HashTable *seen_arrays, + HashTable *seen_objects); + +typedef struct _zend_opcache_serializer_has_unstorable_context { + HashTable *seen_arrays; + HashTable *seen_objects; +} zend_opcache_serializer_has_unstorable_context; + +static bool zend_opcache_serializer_safe_direct_value_has_unstorable_callback( + void *context, + const zval *value) +{ + zend_opcache_serializer_has_unstorable_context *has_unstorable_context = + (zend_opcache_serializer_has_unstorable_context *) context; + + return zend_opcache_serializer_value_has_unstorable_ex( + value, + has_unstorable_context->seen_arrays, + has_unstorable_context->seen_objects + ); +} + +static bool zend_opcache_serializer_hash_has_unstorable_ex( + const HashTable *hash, + HashTable *seen_arrays, + HashTable *seen_objects) +{ + zval *element; + + if (hash == NULL) { + return false; + } + + ZEND_HASH_FOREACH_VAL((HashTable *) hash, element) { + if (Z_TYPE_P(element) == IS_INDIRECT) { + element = Z_INDIRECT_P(element); + if (Z_TYPE_P(element) == IS_UNDEF) { + continue; + } + } + + if (zend_opcache_serializer_value_has_unstorable_ex(element, seen_arrays, seen_objects)) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + +static bool zend_opcache_serializer_safe_direct_state_has_unstorable( + const zval *value, + HashTable *seen_arrays, + HashTable *seen_objects) +{ + zend_class_entry *base_ce = NULL; + zend_opcache_static_cache_safe_direct_state_copy_func_t copy_func; + zend_opcache_static_cache_safe_direct_state_has_unstorable_func_t state_has_unstorable_func; + zend_opcache_serializer_has_unstorable_context context; + + copy_func = zend_opcache_static_cache_safe_direct_copy_func(Z_OBJCE_P(value), &base_ce); + if (copy_func == NULL || + zend_opcache_serializer_has_safe_direct_cache_overrides(Z_OBJCE_P(value), base_ce) + ) { + return false; + } + + state_has_unstorable_func = + zend_opcache_static_cache_safe_direct_state_has_unstorable_func(Z_OBJCE_P(value)); + if (state_has_unstorable_func == NULL) { + return false; + } + + context.seen_arrays = seen_arrays; + context.seen_objects = seen_objects; + + return state_has_unstorable_func( + &context, + value, + zend_opcache_serializer_safe_direct_value_has_unstorable_callback + ); +} + +static bool zend_opcache_serializer_value_has_unstorable_ex( + const zval *value, + HashTable *seen_arrays, + HashTable *seen_objects) +{ + zend_object *object; + zend_ulong key; + zval *property, *end; + + ZVAL_DEREF(value); + switch (Z_TYPE_P(value)) { + case IS_RESOURCE: + return true; + case IS_OBJECT: + if (Z_OBJCE_P(value) == zend_ce_closure) { + return true; + } + + object = Z_OBJ_P(value); + key = (zend_ulong) (uintptr_t) object; + if (zend_hash_index_exists(seen_objects, key)) { + return false; + } + zend_hash_index_add_empty_element(seen_objects, key); + + if (zend_opcache_serializer_safe_direct_state_has_unstorable(value, seen_arrays, seen_objects)) { + return true; + } + + if (object->ce->default_properties_count != 0) { + property = object->properties_table; + end = property + object->ce->default_properties_count; + do { + if (Z_TYPE_P(property) != IS_UNDEF && + zend_opcache_serializer_value_has_unstorable_ex(property, seen_arrays, seen_objects) + ) { + return true; + } + property++; + } while (property != end); + } + + return object->properties != NULL && + zend_opcache_serializer_hash_has_unstorable_ex(object->properties, seen_arrays, seen_objects); + case IS_ARRAY: + key = (zend_ulong) (uintptr_t) Z_ARR_P(value); + if (zend_hash_index_exists(seen_arrays, key)) { + return false; + } + zend_hash_index_add_empty_element(seen_arrays, key); + + return zend_opcache_serializer_hash_has_unstorable_ex(Z_ARRVAL_P(value), seen_arrays, seen_objects); + default: + return false; + } +} + +static zend_always_inline bool zend_opcache_serializer_value_has_unstorable(const zval *value) +{ + HashTable seen_arrays, seen_objects; + bool result; + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&seen_objects, 8, NULL, NULL, 0); + result = zend_opcache_serializer_value_has_unstorable_ex(value, &seen_arrays, &seen_objects); + zend_hash_destroy(&seen_objects); + zend_hash_destroy(&seen_arrays); + + return result; +} + +static zend_always_inline bool zend_opcache_serializer_hash_has_unstorable(const HashTable *hash) +{ + HashTable seen_arrays, seen_objects; + bool result; + + if (hash == NULL) { + return false; + } + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&seen_objects, 8, NULL, NULL, 0); + result = zend_opcache_serializer_hash_has_unstorable_ex(hash, &seen_arrays, &seen_objects); + zend_hash_destroy(&seen_objects); + zend_hash_destroy(&seen_arrays); + + return result; +} + +static zend_always_inline bool zend_opcache_serializer_encode_zval(zend_opcache_serializer_wbuf_t *wb, + const zval *zv); + +static zend_always_inline bool zend_opcache_serializer_encode_property_table(zend_opcache_serializer_wbuf_t *wb, + const HashTable *props) +{ + zend_string *prop_key; + zval *prop_val; + uint32_t prop_count, arr_meta[2] = {0, 0}, key_len; + size_t prop_count_tmp = 0, arr_hdr_offset; + + ZEND_HASH_FOREACH_STR_KEY_VAL(props, prop_key, prop_val) { + if (prop_key != NULL) { + prop_count_tmp++; + } + } ZEND_HASH_FOREACH_END(); + + if (!zend_opcache_serializer_try_u32(prop_count_tmp, &prop_count)) { + return false; + } + + arr_hdr_offset = wb->len; + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_ARRAY, + ZEND_OPCACHE_SERIALIZER_ARRAY_MAP, 0); + arr_meta[0] = prop_count; + zend_opcache_serializer_wbuf_write(wb, arr_meta, sizeof(arr_meta)); + + ZEND_HASH_FOREACH_STR_KEY_VAL(props, prop_key, prop_val) { + if (prop_key == NULL) { + continue; + } + + if (Z_TYPE_P(prop_val) == IS_INDIRECT) { + prop_val = Z_INDIRECT_P(prop_val); + } + + if (!zend_opcache_serializer_try_u32(ZSTR_LEN(prop_key), &key_len) || + !zend_opcache_serializer_write_string_key_entry(wb, ZSTR_VAL(prop_key), key_len) + ) { + return false; + } + + if (Z_TYPE_P(prop_val) == IS_UNDEF) { + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_UNDEF, 0, 0); + } else if (!zend_opcache_serializer_encode_zval(wb, prop_val)) { + return false; + } + } ZEND_HASH_FOREACH_END(); + + return zend_opcache_serializer_patch_hdr(wb, arr_hdr_offset); +} + +static zend_always_inline bool zend_opcache_serializer_update_object_property(zend_object *object, + zend_string *key, zval *prop_val) +{ + const char *class_name, *prop_name; + zend_string *cname; + zend_class_entry *ce; + size_t prop_name_len; + + if (ZSTR_LEN(key) > 0 && ZSTR_VAL(key)[0] == '\0') { + if (zend_unmangle_property_name_ex(key, &class_name, &prop_name, &prop_name_len) == SUCCESS) { + if (class_name[0] != '*') { + cname = zend_string_init(class_name, strlen(class_name), 0); + ce = zend_lookup_class(cname); + if (ce != NULL) { + zend_update_property(ce, object, prop_name, prop_name_len, prop_val); + } + + zend_string_release_ex(cname, 0); + } else { + zend_update_property(object->ce, object, prop_name, prop_name_len, prop_val); + } + } + } else { + zend_update_property(object->ce, object, ZSTR_VAL(key), ZSTR_LEN(key), prop_val); + } + + return !EG(exception); +} + +static zend_always_inline bool zend_opcache_serializer_restore_object_properties(zval *object, const HashTable *props) +{ + zend_string *prop_name; + zval *prop_val; + + ZEND_HASH_FOREACH_STR_KEY_VAL(props, prop_name, prop_val) { + if (prop_name == NULL || Z_TYPE_P(prop_val) == IS_REFERENCE) { + continue; + } + + if (!zend_opcache_serializer_update_object_property(Z_OBJ_P(object), prop_name, prop_val)) { + return false; + } + } ZEND_HASH_FOREACH_END(); + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_encode_safe_direct_state_payload( + zend_opcache_serializer_wbuf_t *wb, zval *state_zv, const HashTable *props) +{ + bool has_unstorable_state, has_unstorable_props, ok; + + has_unstorable_state = zend_opcache_serializer_value_has_unstorable(state_zv); + has_unstorable_props = zend_opcache_serializer_hash_has_unstorable(props); + if (has_unstorable_state || has_unstorable_props) { + wb->failed_unstorable = true; + ok = false; + } else { + ok = zend_opcache_serializer_encode_zval(wb, state_zv) && + zend_opcache_serializer_encode_property_table(wb, props); + } + + zval_ptr_dtor(state_zv); + ZVAL_UNDEF(state_zv); + + return ok; +} + +static zend_always_inline bool zend_opcache_serializer_encode_safe_direct_registered_payload( + zend_opcache_serializer_wbuf_t *wb, const zval *zv) +{ + zend_opcache_static_cache_safe_direct_state_serialize_func_t serialize_func; + zval state_zv; + HashTable *props; + + serialize_func = zend_opcache_static_cache_safe_direct_state_serialize_func(Z_OBJCE_P(zv)); + if (serialize_func == NULL) { + return false; + } + + ZVAL_UNDEF(&state_zv); + if (!serialize_func(zv, &state_zv) || Z_TYPE(state_zv) != IS_ARRAY) { + if (Z_TYPE(state_zv) != IS_UNDEF) { + zval_ptr_dtor(&state_zv); + } + + return false; + } + + props = zend_std_get_properties(Z_OBJ_P(zv)); + + return zend_opcache_serializer_encode_safe_direct_state_payload(wb, &state_zv, props); +} + +static zend_always_inline bool zend_opcache_serializer_try_encode_safe_direct_object( + zend_opcache_serializer_wbuf_t *wb, const zval *zv, size_t hdr_offset, + zend_class_entry *ce, zend_string *class_name, uint32_t name_len) +{ + zend_class_entry *base_ce = NULL; + zend_opcache_static_cache_safe_direct_state_copy_func_t copy_func; + bool ok; + + copy_func = zend_opcache_static_cache_safe_direct_copy_func(ce, &base_ce); + if (copy_func == NULL || + zend_opcache_serializer_has_safe_direct_cache_overrides(ce, base_ce) + ) { + return false; + } + + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_OBJECT, + ZEND_OPCACHE_SERIALIZER_OBJ_SAFE_DIRECT, 0); + + ok = zend_opcache_serializer_write_bytes_entry(wb, ZSTR_VAL(class_name), name_len); + if (ok) { + ok = zend_opcache_serializer_encode_safe_direct_registered_payload(wb, zv); + } + + if (!ok) { + if (EG(exception)) { + return false; + } + + wb->len = hdr_offset; + + return false; + } + + return zend_opcache_serializer_patch_hdr(wb, hdr_offset); +} + +static zend_always_inline bool zend_opcache_serializer_array_can_use_packed(const HashTable *ht) +{ + zend_string *key; + zend_ulong expected_idx = 0, h; + zval *val; + + if (!HT_IS_PACKED(ht)) { + return false; + } + + ZEND_HASH_FOREACH_KEY_VAL((HashTable *) ht, h, key, val) { + (void) val; + + if (key != NULL || h != expected_idx++) { + return false; + } + } ZEND_HASH_FOREACH_END(); + + return true; +} + +static bool zend_opcache_serializer_encode_array(zend_opcache_serializer_wbuf_t *wb, + const HashTable *ht) +{ + zend_opcache_serializer_hdr_t *hdr; + zend_ulong h; + zend_string *key; + zval *val; + uint64_t idx_value; + uint32_t count, arr_meta[2], key_len; + uint8_t flags; + size_t hdr_offset; + bool use_packed; + unsigned char key_buf[16]; + + if (!zend_opcache_serializer_try_u32(zend_hash_num_elements(ht), &count)) { + return false; + } + + if (ht->nNextFreeElement > UINT32_MAX) { + return false; + } + + if (!zend_opcache_serializer_enter_array(wb, ht)) { + return false; + } + + use_packed = zend_opcache_serializer_array_can_use_packed(ht); + flags = use_packed ? ZEND_OPCACHE_SERIALIZER_ARRAY_PACKED : ZEND_OPCACHE_SERIALIZER_ARRAY_MAP; + hdr_offset = wb->len; + hdr = zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_ARRAY, flags, 0); + ZEND_ASSERT(hdr != NULL); + + arr_meta[0] = count; + arr_meta[1] = (uint32_t) ht->nNextFreeElement; + zend_opcache_serializer_wbuf_write(wb, arr_meta, sizeof(arr_meta)); + + if (use_packed) { + ZEND_HASH_PACKED_FOREACH_VAL((HashTable *) ht, val) { + if (!zend_opcache_serializer_encode_zval(wb, val)) { + zend_opcache_serializer_leave_array(wb, ht); + + return false; + } + } ZEND_HASH_FOREACH_END(); + } else { + ZEND_HASH_FOREACH_KEY_VAL((HashTable *) ht, h, key, val) { + if (key != NULL) { + if (!zend_opcache_serializer_try_u32(ZSTR_LEN(key), &key_len) || + !zend_opcache_serializer_write_string_key_entry(wb, ZSTR_VAL(key), key_len) + ) { + zend_opcache_serializer_leave_array(wb, ht); + + return false; + } + } else { + memset(key_buf, 0, sizeof(key_buf)); + idx_value = (uint64_t) h; + memcpy(key_buf + 8, &idx_value, sizeof(uint64_t)); + + zend_opcache_serializer_wbuf_write(wb, key_buf, sizeof(key_buf)); + } + + if (!zend_opcache_serializer_encode_zval(wb, val)) { + zend_opcache_serializer_leave_array(wb, ht); + + return false; + } + } ZEND_HASH_FOREACH_END(); + } + + zend_opcache_serializer_leave_array(wb, ht); + + return zend_opcache_serializer_patch_hdr(wb, hdr_offset); +} + +static bool zend_opcache_serializer_encode_object(zend_opcache_serializer_wbuf_t *wb, + const zval *zv) +{ + zend_class_entry *ce; + zend_string *class_name, *resolved_name, *prop_key; + zval retval, sleep_rv, func_name, *sleep_entry, *found_val, *prop_val; + php_serialize_data_t var_hash; + HashTable *props, *sleep_ht; + smart_str ser = {0}; + uint32_t name_len, prop_count, arr_meta[2] = {0, 0}, key_len, ser_len; + size_t hdr_offset, arr_hdr_offset, property_count; + bool has_sleep, resolved_name_owned; + int call_ret; + + ce = Z_OBJCE_P(zv); + + class_name = ce->name; + if (!zend_opcache_serializer_try_u32(ZSTR_LEN(class_name), &name_len)) { + return false; + } + + if (!zend_opcache_serializer_mark_object(wb, zv)) { + return false; + } + + hdr_offset = wb->len; + ZVAL_UNDEF(&retval); + if (zend_opcache_serializer_try_encode_safe_direct_object(wb, zv, hdr_offset, ce, class_name, name_len)) { + return true; + } + + if (EG(exception)) { + return false; + } + + if (wb->failed_unstorable) { + wb->len = hdr_offset; + + return false; + } + + if (ce->__serialize != NULL) { + zend_call_known_instance_method_with_0_params(ce->__serialize, Z_OBJ_P(zv), &retval); + if (EG(exception)) { + if (Z_TYPE(retval) != IS_UNDEF) { + zval_ptr_dtor(&retval); + } + + return false; + } + + if (Z_TYPE(retval) == IS_ARRAY) { + if (zend_opcache_serializer_value_has_unstorable(&retval)) { + wb->failed_unstorable = true; + zval_ptr_dtor(&retval); + wb->len = hdr_offset; + + return false; + } + + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_OBJECT, + ZEND_OPCACHE_SERIALIZER_OBJ_SERIALIZE, 0); + if (!zend_opcache_serializer_write_bytes_entry(wb, ZSTR_VAL(class_name), name_len) || + !zend_opcache_serializer_encode_array(wb, Z_ARRVAL(retval)) + ) { + zval_ptr_dtor(&retval); + + if (EG(exception)) { + return false; + } + + if (wb->failed_unstorable) { + wb->len = hdr_offset; + + return false; + } + + wb->len = hdr_offset; + + goto fallback_php; + } + + zval_ptr_dtor(&retval); + + return zend_opcache_serializer_patch_hdr(wb, hdr_offset); + } + + if (Z_TYPE(retval) != IS_UNDEF) { + zval_ptr_dtor(&retval); + } + + wb->len = hdr_offset; + + goto fallback_php; + } + + props = NULL; + sleep_ht = NULL; + has_sleep = false; + ZVAL_UNDEF(&sleep_rv); + + if (zend_hash_str_exists(&ce->function_table, "__sleep", sizeof("__sleep") - 1)) { + ZVAL_STRINGL(&func_name, "__sleep", sizeof("__sleep") - 1); + call_ret = call_user_function(CG(function_table), (zval *) zv, &func_name, &sleep_rv, 0, NULL); + zval_ptr_dtor_str(&func_name); + + if (EG(exception)) { + if (Z_TYPE(sleep_rv) != IS_UNDEF) { + zval_ptr_dtor(&sleep_rv); + } + + return false; + } + + if (call_ret == SUCCESS && Z_TYPE(sleep_rv) == IS_ARRAY) { + has_sleep = true; + sleep_ht = Z_ARRVAL(sleep_rv); + } else if (Z_TYPE(sleep_rv) != IS_UNDEF) { + zval_ptr_dtor(&sleep_rv); + ZVAL_UNDEF(&sleep_rv); + } + } + + props = zend_get_properties_for((zval *) zv, ZEND_PROP_PURPOSE_SERIALIZE); + if (props != NULL) { + property_count = has_sleep + ? zend_opcache_serializer_sleep_property_count(sleep_ht) + : zend_hash_num_elements(props) + ; + if (!zend_opcache_serializer_try_u32(property_count, &prop_count)) { + zend_release_properties(props); + + if (has_sleep) { + zval_ptr_dtor(&sleep_rv); + } + + return false; + } + + if (!has_sleep && zend_opcache_serializer_hash_has_unstorable(props)) { + wb->failed_unstorable = true; + zend_release_properties(props); + wb->len = hdr_offset; + + return false; + } + + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_OBJECT, + ZEND_OPCACHE_SERIALIZER_OBJ_PROPS, 0); + if (!zend_opcache_serializer_write_bytes_entry(wb, ZSTR_VAL(class_name), name_len)) { + zend_release_properties(props); + + if (has_sleep) { + zval_ptr_dtor(&sleep_rv); + } + + return false; + } + + arr_hdr_offset = wb->len; + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_ARRAY, + ZEND_OPCACHE_SERIALIZER_ARRAY_MAP, 0 + ); + arr_meta[0] = prop_count; + zend_opcache_serializer_wbuf_write(wb, arr_meta, sizeof(arr_meta)); + + if (has_sleep) { + ZEND_HASH_FOREACH_VAL(sleep_ht, sleep_entry) { + if (Z_TYPE_P(sleep_entry) != IS_STRING) { + continue; + } + + resolved_name = Z_STR_P(sleep_entry); + resolved_name_owned = false; + found_val = zend_opcache_serializer_find_sleep_property(props, ce, + Z_STR_P(sleep_entry), &resolved_name, &resolved_name_owned) + ; + + if (!zend_opcache_serializer_try_u32(ZSTR_LEN(resolved_name), &key_len) || + !zend_opcache_serializer_write_string_key_entry(wb, ZSTR_VAL(resolved_name), key_len) + ) { + if (resolved_name_owned) { + zend_string_release(resolved_name); + } + + zend_release_properties(props); + zval_ptr_dtor(&sleep_rv); + + return false; + } + + if (resolved_name_owned) { + zend_string_release(resolved_name); + } + + if (found_val != NULL && Z_TYPE_P(found_val) == IS_INDIRECT) { + found_val = Z_INDIRECT_P(found_val); + } + + if (found_val != NULL && + Z_TYPE_P(found_val) != IS_UNDEF && + zend_opcache_serializer_value_has_unstorable(found_val) + ) { + wb->failed_unstorable = true; + zend_release_properties(props); + zval_ptr_dtor(&sleep_rv); + wb->len = hdr_offset; + + return false; + } + + if (found_val == NULL || Z_TYPE_P(found_val) == IS_UNDEF) { + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_UNDEF, 0, 0); + } else if (!zend_opcache_serializer_encode_zval(wb, found_val)) { + zend_release_properties(props); + zval_ptr_dtor(&sleep_rv); + + if (EG(exception)) { + return false; + } + + if (wb->failed_unstorable) { + wb->len = hdr_offset; + + return false; + } + + wb->len = hdr_offset; + + goto fallback_php; + } + } ZEND_HASH_FOREACH_END(); + } else { + ZEND_HASH_FOREACH_STR_KEY_VAL(props, prop_key, prop_val) { + if (prop_key == NULL) { + continue; + } + + if (Z_TYPE_P(prop_val) == IS_INDIRECT) { + prop_val = Z_INDIRECT_P(prop_val); + } + + if (!zend_opcache_serializer_try_u32(ZSTR_LEN(prop_key), &key_len) || + !zend_opcache_serializer_write_string_key_entry(wb, ZSTR_VAL(prop_key), key_len)) { + zend_release_properties(props); + + return false; + } + + if (Z_TYPE_P(prop_val) == IS_UNDEF) { + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_UNDEF, 0, 0); + } else if (!zend_opcache_serializer_encode_zval(wb, prop_val)) { + zend_release_properties(props); + + if (EG(exception)) { + return false; + } + + if (wb->failed_unstorable) { + wb->len = hdr_offset; + + return false; + } + + wb->len = hdr_offset; + + goto fallback_php; + } + } ZEND_HASH_FOREACH_END(); + + zend_release_properties(props); + + if (!zend_opcache_serializer_patch_hdr(wb, arr_hdr_offset)) { + return false; + } + + return zend_opcache_serializer_patch_hdr(wb, hdr_offset); + } + + if (has_sleep) { + zval_ptr_dtor(&sleep_rv); + } + + zend_release_properties(props); + + if (!zend_opcache_serializer_patch_hdr(wb, arr_hdr_offset)) { + return false; + } + + return zend_opcache_serializer_patch_hdr(wb, hdr_offset); + } + +fallback_php: + if (zend_opcache_serializer_value_has_unstorable(zv)) { + wb->failed_unstorable = true; + + return false; + } + + PHP_VAR_SERIALIZE_INIT(var_hash); + php_var_serialize(&ser, (zval *) zv, &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + if (ser.s == NULL) { + return false; + } + + if (!zend_opcache_serializer_try_u32(ZSTR_LEN(ser.s), &ser_len)) { + smart_str_free(&ser); + + return false; + } + + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_OBJECT, + ZEND_OPCACHE_SERIALIZER_OBJ_PHP_SER, 0); + + if (!zend_opcache_serializer_write_bytes_entry(wb, ZSTR_VAL(class_name), name_len) || + !zend_opcache_serializer_write_bytes_entry(wb, ZSTR_VAL(ser.s), ser_len) + ) { + smart_str_free(&ser); + + return false; + } + + smart_str_free(&ser); + + return zend_opcache_serializer_patch_hdr(wb, hdr_offset); +} + +static zend_always_inline bool zend_opcache_serializer_encode_zval(zend_opcache_serializer_wbuf_t *wb, const zval *zv) +{ + int64_t lval; + double dval; + + if (wb->depth >= ZEND_OPCACHE_SERIALIZER_MAX_DEPTH) { + return false; + } + + wb->depth++; + + switch (Z_TYPE_P(zv)) { + case IS_UNDEF: + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_UNDEF, 0, 0); + break; + case IS_NULL: + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_NULL, 0, 0); + break; + case IS_FALSE: + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_FALSE, 0, 0); + break; + case IS_TRUE: + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_TRUE, 0, 0); + break; + case IS_LONG: + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_LONG, 0, sizeof(int64_t)); + lval = (int64_t) Z_LVAL_P(zv); + zend_opcache_serializer_wbuf_write(wb, &lval, sizeof(int64_t)); + + break; + case IS_DOUBLE: + zend_opcache_serializer_wbuf_write_hdr(wb, ZEND_OPCACHE_SERIALIZER_TYPE_DOUBLE, 0, sizeof(double)); + dval = Z_DVAL_P(zv); + zend_opcache_serializer_wbuf_write(wb, &dval, sizeof(double)); + + break; + case IS_STRING: + if (!zend_opcache_serializer_encode_string(wb, Z_STR_P(zv))) { + wb->depth--; + + return false; + } + + break; + case IS_ARRAY: + if (!zend_opcache_serializer_encode_array(wb, Z_ARRVAL_P(zv))) { + wb->depth--; + + return false; + } + + break; + case IS_OBJECT: + if (!zend_opcache_serializer_encode_object(wb, zv)) { + wb->depth--; + + return false; + } + + break; + case IS_RESOURCE: + wb->failed_unstorable = true; + wb->depth--; + + return false; + default: + wb->depth--; + + return false; + } + + wb->depth--; + + return true; +} + +static zend_always_inline bool zend_opcache_serialize_ex( + unsigned char **buf, + size_t *buf_len, + const zval *value, + bool *failed_unstorable) +{ + zend_opcache_serializer_wbuf_t wb; + bool ok; + + zend_opcache_serializer_wbuf_init(&wb, 256); + ok = zend_opcache_serializer_encode_zval(&wb, value); + if (failed_unstorable != NULL) { + *failed_unstorable = wb.failed_unstorable; + } + zend_opcache_serializer_wbuf_destroy(&wb); + + if (!ok) { + efree(wb.data); + *buf = NULL; + *buf_len = 0; + + return false; + } + + *buf = wb.data; + *buf_len = wb.len; + + return true; +} + +static zend_always_inline bool zend_opcache_serialize(unsigned char **buf, size_t *buf_len, + const zval *value) +{ + return zend_opcache_serialize_ex(buf, buf_len, value, NULL); +} + +static zend_always_inline bool zend_opcache_serializer_rbuf_check(const zend_opcache_serializer_rbuf_t *rb, size_t need) +{ + return rb->pos + need <= rb->len; +} + +static zend_always_inline const void *zend_opcache_serializer_rbuf_read(zend_opcache_serializer_rbuf_t *rb, size_t size) +{ + const void *ptr; + size_t aligned_size; + + aligned_size = ZEND_OPCACHE_SERIALIZER_ALIGN8(size); + if (!zend_opcache_serializer_rbuf_check(rb, aligned_size)) { + return NULL; + } + + ptr = rb->data + rb->pos; + rb->pos += aligned_size; + + return ptr; +} + +static zend_always_inline bool zend_opcache_serializer_rbuf_read_copy( + zend_opcache_serializer_rbuf_t *rb, void *dst, size_t size) +{ + const void *src; + + src = zend_opcache_serializer_rbuf_read(rb, size); + if (src == NULL) { + return false; + } + + memcpy(dst, src, size); + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_rbuf_read_hdr( + zend_opcache_serializer_rbuf_t *rb, zend_opcache_serializer_hdr_t *hdr) +{ + return zend_opcache_serializer_rbuf_read_copy(rb, hdr, sizeof(*hdr)); +} + +static zend_always_inline bool zend_opcache_serializer_rbuf_skip(zend_opcache_serializer_rbuf_t *rb, size_t size) +{ + size_t aligned_size; + + aligned_size = ZEND_OPCACHE_SERIALIZER_ALIGN8(size); + if (!zend_opcache_serializer_rbuf_check(rb, aligned_size)) { + return false; + } + + rb->pos += aligned_size; + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_decode_zval(zend_opcache_serializer_rbuf_t *rb, + zval *dst); + +static inline bool zend_opcache_serializer_decode_zval_suppressed(zend_opcache_serializer_rbuf_t *rb, zval *dst) +{ + bool result; + + rb->capture_suppression++; + result = zend_opcache_serializer_decode_zval(rb, dst); + rb->capture_suppression--; + + return result; +} + +static zend_always_inline bool zend_opcache_serializer_decode_string_to_zval(zend_opcache_serializer_rbuf_t *rb, + zval *dst, const zend_opcache_serializer_hdr_t *hdr) +{ + const unsigned char *payload; + uint32_t string_len; + + if (hdr->data_size < 9) { + return false; + } + + payload = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, hdr->data_size); + if (payload == NULL) { + return false; + } + + memcpy(&string_len, payload, sizeof(uint32_t)); + if ((size_t) string_len + 9 > hdr->data_size) { + return false; + } + + ZVAL_STRINGL(dst, (const char *) (payload + 8), string_len); + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_read_object_class_name(zend_opcache_serializer_rbuf_t *rb, + const char **name_str_out, uint32_t *name_len_out) +{ + const unsigned char *name_data; + const char *name_str; + uint32_t name_len; + + name_data = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, 8); + if (name_data == NULL) { + return false; + } + + memcpy(&name_len, name_data, sizeof(uint32_t)); + name_str = (const char *) zend_opcache_serializer_rbuf_read(rb, (size_t) name_len + 1); + if (name_str == NULL) { + return false; + } + + *name_str_out = name_str; + *name_len_out = name_len; + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_decode_object_payload_with_ce( + zend_opcache_serializer_rbuf_t *rb, zval *dst, uint8_t obj_flags, zend_class_entry *ce) +{ + const unsigned char *arr_meta, *key_data; + const char *key_str; + zend_opcache_serializer_hdr_t arr_hdr; + zend_class_entry *base_ce = NULL; + zend_property_info *prop_info; + zend_opcache_static_cache_safe_direct_state_copy_func_t copy_func; + zend_opcache_static_cache_safe_direct_state_unserialize_func_t unserialize_func; + zval props_zv, state_zv, data_arr, + val, *existing, *target, func_name, wakeup_rv; + HashTable *obj_ht; + uint32_t prop_count, index, key_len; + uint8_t key_type; + bool ok; + + if ((obj_flags & ZEND_OPCACHE_SERIALIZER_OBJ_SAFE_DIRECT) != 0) { + copy_func = zend_opcache_static_cache_safe_direct_copy_func(ce, &base_ce); + if (copy_func == NULL || + zend_opcache_serializer_has_safe_direct_cache_overrides(ce, base_ce) + ) { + ZVAL_NULL(dst); + + return false; + } + + unserialize_func = zend_opcache_static_cache_safe_direct_state_unserialize_func(ce); + if (unserialize_func == NULL) { + ZVAL_NULL(dst); + + return false; + } + + ZVAL_UNDEF(&state_zv); + ZVAL_UNDEF(&props_zv); + if (!zend_opcache_serializer_decode_zval_suppressed(rb, &state_zv) || + !zend_opcache_serializer_decode_zval_suppressed(rb, &props_zv) || + Z_TYPE(state_zv) != IS_ARRAY || + Z_TYPE(props_zv) != IS_ARRAY + ) { + if (Z_TYPE(state_zv) != IS_UNDEF) { + zval_ptr_dtor(&state_zv); + } + + if (Z_TYPE(props_zv) != IS_UNDEF) { + zval_ptr_dtor(&props_zv); + } + + ZVAL_NULL(dst); + + return false; + } + + if (object_init_ex(dst, ce) != SUCCESS) { + zval_ptr_dtor(&state_zv); + zval_ptr_dtor(&props_zv); + ZVAL_NULL(dst); + + return false; + } + + ok = unserialize_func(dst, &state_zv); + zval_ptr_dtor(&state_zv); + if (!ok || EG(exception)) { + zval_ptr_dtor(&props_zv); + zval_ptr_dtor(dst); + ZVAL_NULL(dst); + + return false; + } + + ok = zend_opcache_serializer_restore_object_properties(dst, Z_ARRVAL(props_zv)); + zval_ptr_dtor(&props_zv); + if (!ok) { + zval_ptr_dtor(dst); + ZVAL_NULL(dst); + + return false; + } + + zend_opcache_serializer_capture_decoded_reachable_value(rb, dst); + + return true; + } + + if ((obj_flags & ZEND_OPCACHE_SERIALIZER_OBJ_SERIALIZE) != 0) { + ZVAL_UNDEF(&data_arr); + if (!zend_opcache_serializer_decode_zval_suppressed(rb, &data_arr) || Z_TYPE(data_arr) != IS_ARRAY) { + if (Z_TYPE(data_arr) != IS_UNDEF) { + zval_ptr_dtor(&data_arr); + } + ZVAL_NULL(dst); + + return false; + } + + if (object_init_ex(dst, ce) != SUCCESS) { + zval_ptr_dtor(&data_arr); + ZVAL_NULL(dst); + + return false; + } + + if (ce->__unserialize != NULL) { + zend_call_known_instance_method_with_1_params(ce->__unserialize, Z_OBJ_P(dst), NULL, &data_arr); + if (EG(exception)) { + zval_ptr_dtor(&data_arr); + zval_ptr_dtor(dst); + ZVAL_NULL(dst); + + return false; + } + } + + zval_ptr_dtor(&data_arr); + zend_opcache_serializer_capture_decoded_reachable_value(rb, dst); + + return true; + } + + if ((obj_flags & ZEND_OPCACHE_SERIALIZER_OBJ_PROPS) != 0) { + if (object_init_ex(dst, ce) != SUCCESS) { + ZVAL_NULL(dst); + + return false; + } + + if (!zend_opcache_serializer_rbuf_read_hdr(rb, &arr_hdr) || arr_hdr.type != ZEND_OPCACHE_SERIALIZER_TYPE_ARRAY) { + zval_ptr_dtor(dst); + ZVAL_NULL(dst); + + return false; + } + + arr_meta = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, 8); + if (arr_meta == NULL) { + zval_ptr_dtor(dst); + ZVAL_NULL(dst); + + return false; + } + + memcpy(&prop_count, arr_meta, sizeof(uint32_t)); + obj_ht = Z_OBJ_P(dst)->handlers->get_properties(Z_OBJ_P(dst)); + if (obj_ht == NULL) { + obj_ht = zend_std_get_properties(Z_OBJ_P(dst)); + } + + for (index = 0; index < prop_count; index++) { + if (!zend_opcache_serializer_rbuf_check(rb, 8)) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + key_type = rb->data[rb->pos]; + if (key_type != 1) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + key_data = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, 8); + if (key_data == NULL) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + memcpy(&key_len, key_data + 4, sizeof(uint32_t)); + + key_str = (const char *) zend_opcache_serializer_rbuf_read(rb, (size_t) key_len + 1); + if (key_str == NULL) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + ZVAL_UNDEF(&val); + if (!zend_opcache_serializer_decode_zval(rb, &val)) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + existing = zend_hash_str_find(obj_ht, key_str, key_len); + if (existing != NULL) { + target = existing; + if (Z_TYPE_P(target) == IS_INDIRECT) { + target = Z_INDIRECT_P(target); + } + + if (Z_TYPE(val) != IS_UNDEF) { + prop_info = zend_get_typed_property_info_for_slot(Z_OBJ_P(dst), target); + if (prop_info != NULL && !zend_verify_prop_assignable_by_ref(prop_info, &val, true)) { + zval_ptr_dtor(&val); + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + zval_ptr_dtor(target); + ZVAL_COPY_VALUE(target, &val); + } else { + zval_ptr_dtor(&val); + } + } else if (Z_TYPE(val) != IS_UNDEF) { + zend_hash_str_add_new(obj_ht, key_str, key_len, &val); + } else { + zval_ptr_dtor(&val); + } + } + + if (zend_hash_str_exists(&ce->function_table, "__wakeup", sizeof("__wakeup") - 1) && + !EG(exception) + ) { + ZVAL_UNDEF(&wakeup_rv); + ZVAL_STRINGL(&func_name, "__wakeup", sizeof("__wakeup") - 1); + call_user_function(CG(function_table), dst, &func_name, &wakeup_rv, 0, NULL); + zval_ptr_dtor_str(&func_name); + + if (Z_TYPE(wakeup_rv) != IS_UNDEF) { + zval_ptr_dtor(&wakeup_rv); + } + + if (EG(exception)) { + zval_ptr_dtor(dst); + ZVAL_NULL(dst); + + return false; + } + } + + return true; + } + + ZVAL_NULL(dst); + + return false; +} + +static bool zend_opcache_serializer_decode_object(zend_opcache_serializer_rbuf_t *rb, + zval *dst, const zend_opcache_serializer_hdr_t *hdr) +{ + const unsigned char *ser_meta, *ser_data, *position, *end; + const char *name_str; + zend_string *class_name; + zend_class_entry *ce; + php_unserialize_data_t var_hash; + uint32_t name_len, ser_len; + uint8_t obj_flags; + int result; + + name_str = NULL; + name_len = 0; + obj_flags = hdr->flags; + + if (!zend_opcache_serializer_read_object_class_name(rb, &name_str, &name_len)) { + return false; + } + + if ((obj_flags & ZEND_OPCACHE_SERIALIZER_OBJ_PHP_SER) != 0) { + ser_meta = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, 8); + if (ser_meta == NULL) { + return false; + } + + memcpy(&ser_len, ser_meta, sizeof(uint32_t)); + + ser_data = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, (size_t) ser_len + 1); + if (ser_data == NULL) { + return false; + } + + position = ser_data; + end = position + ser_len; + PHP_VAR_UNSERIALIZE_INIT(var_hash); + result = php_var_unserialize(dst, &position, end, &var_hash); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + if (result != 1 || position != end) { + ZVAL_NULL(dst); + + return false; + } + + zend_opcache_serializer_capture_decoded_reachable_value(rb, dst); + + return true; + } + + class_name = zend_string_init(name_str, name_len, 0); + ce = zend_lookup_class(class_name); + zend_string_release(class_name); + if (ce == NULL) { + ZVAL_NULL(dst); + + return false; + } + + return zend_opcache_serializer_decode_object_payload_with_ce(rb, dst, obj_flags, ce); +} + +static bool zend_opcache_serializer_decode_array(zend_opcache_serializer_rbuf_t *rb, zval *dst, + const zend_opcache_serializer_hdr_t *hdr) +{ + const char *key_str; + const unsigned char *meta, *key_data, *key_block; + zval elem; + uint64_t int_key; + uint32_t count, next_free, index, key_len; + uint8_t key_type; + bool is_packed; + + if (hdr->data_size < 8) { + return false; + } + + meta = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, 8); + if (meta == NULL) { + return false; + } + + memcpy(&count, meta, sizeof(uint32_t)); + memcpy(&next_free, meta + 4, sizeof(uint32_t)); + is_packed = (hdr->flags & ZEND_OPCACHE_SERIALIZER_ARRAY_PACKED) != 0; + + array_init_size(dst, count); + + if (is_packed) { + for (index = 0; index < count; index++) { + ZVAL_UNDEF(&elem); + + if (index + 1 < count && zend_opcache_serializer_rbuf_check(rb, 64)) { + ZEND_OPCACHE_SERIALIZER_PREFETCH_R(rb->data + rb->pos + 64); + } + + if (!zend_opcache_serializer_decode_zval(rb, &elem)) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + zend_hash_next_index_insert_new(Z_ARRVAL_P(dst), &elem); + } + } else { + for (index = 0; index < count; index++) { + if (zend_opcache_serializer_rbuf_check(rb, 64)) { + ZEND_OPCACHE_SERIALIZER_PREFETCH_R(rb->data + rb->pos + 64); + } + + if (!zend_opcache_serializer_rbuf_check(rb, 8)) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + key_type = rb->data[rb->pos]; + if (key_type == 0) { + key_block = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, 16); + if (key_block == NULL) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + memcpy(&int_key, key_block + 8, sizeof(uint64_t)); + + ZVAL_UNDEF(&elem); + if (!zend_opcache_serializer_decode_zval(rb, &elem)) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + zend_hash_index_add_new(Z_ARRVAL_P(dst), (zend_ulong) int_key, &elem); + } else if (key_type == 1) { + key_data = (const unsigned char *) zend_opcache_serializer_rbuf_read(rb, 8); + if (key_data == NULL) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + memcpy(&key_len, key_data + 4, sizeof(uint32_t)); + + key_str = (const char *) zend_opcache_serializer_rbuf_read(rb, (size_t) key_len + 1); + if (key_str == NULL) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + ZVAL_UNDEF(&elem); + if (!zend_opcache_serializer_decode_zval(rb, &elem)) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + zend_hash_str_add_new(Z_ARRVAL_P(dst), key_str, key_len, &elem); + } else { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + } + } + + Z_ARRVAL_P(dst)->nNextFreeElement = next_free; + + return true; +} + +static zend_always_inline bool zend_opcache_serializer_decode_zval(zend_opcache_serializer_rbuf_t *rb, zval *dst) +{ + zend_opcache_serializer_hdr_t hdr; + const void *payload; + int64_t lval; + double dval; + bool previous_skip_decoded_value_capture, skip_decoded_value_capture; + + if (rb->depth >= ZEND_OPCACHE_SERIALIZER_MAX_DEPTH) { + ZVAL_NULL(dst); + + return false; + } + + previous_skip_decoded_value_capture = rb->skip_decoded_value_capture; + rb->skip_decoded_value_capture = false; + rb->depth++; + + if (!zend_opcache_serializer_rbuf_read_hdr(rb, &hdr)) { + ZVAL_NULL(dst); + rb->depth--; + + return false; + } + + switch (hdr.type) { + case ZEND_OPCACHE_SERIALIZER_TYPE_UNDEF: + ZVAL_UNDEF(dst); + break; + case ZEND_OPCACHE_SERIALIZER_TYPE_NULL: + ZVAL_NULL(dst); + break; + case ZEND_OPCACHE_SERIALIZER_TYPE_FALSE: + ZVAL_FALSE(dst); + break; + case ZEND_OPCACHE_SERIALIZER_TYPE_TRUE: + ZVAL_TRUE(dst); + break; + case ZEND_OPCACHE_SERIALIZER_TYPE_LONG: { + payload = zend_opcache_serializer_rbuf_read(rb, sizeof(int64_t)); + if (payload == NULL) { + ZVAL_NULL(dst); + rb->depth--; + + return false; + } + + memcpy(&lval, payload, sizeof(int64_t)); + + ZVAL_LONG(dst, (zend_long) lval); + break; + } + case ZEND_OPCACHE_SERIALIZER_TYPE_DOUBLE: { + payload = zend_opcache_serializer_rbuf_read(rb, sizeof(double)); + if (payload == NULL) { + ZVAL_NULL(dst); + rb->depth--; + + return false; + } + + memcpy(&dval, payload, sizeof(double)); + + ZVAL_DOUBLE(dst, dval); + break; + } + case ZEND_OPCACHE_SERIALIZER_TYPE_STRING: + if (!zend_opcache_serializer_decode_string_to_zval(rb, dst, &hdr)) { + ZVAL_NULL(dst); + rb->depth--; + + return false; + } + break; + case ZEND_OPCACHE_SERIALIZER_TYPE_ARRAY: + if (!zend_opcache_serializer_decode_array(rb, dst, &hdr)) { + rb->depth--; + + return false; + } + break; + case ZEND_OPCACHE_SERIALIZER_TYPE_OBJECT: + if (!zend_opcache_serializer_decode_object(rb, dst, &hdr)) { + rb->depth--; + + return false; + } + break; + default: + ZVAL_NULL(dst); + rb->depth--; + + return false; + } + + skip_decoded_value_capture = rb->skip_decoded_value_capture; + rb->skip_decoded_value_capture = previous_skip_decoded_value_capture; + if (rb->capture_suppression == 0 && + !skip_decoded_value_capture && + (Z_TYPE_P(dst) == IS_ARRAY || Z_TYPE_P(dst) == IS_OBJECT) + ) { + zend_opcache_serializer_capture_decoded_value(rb, dst); + } + + rb->depth--; + + return true; +} + +static zend_always_inline bool zend_opcache_unserialize_ex( + zval *dst, + const unsigned char *buf, + size_t buf_len, + zend_opcache_serializer_capture_func_t capture_decoded_value, + zend_opcache_serializer_capture_func_t capture_decoded_reachable_value +) +{ + zend_opcache_serializer_rbuf_t rb; + + rb.data = buf; + rb.capture_decoded_value = capture_decoded_value; + rb.capture_decoded_reachable_value = capture_decoded_reachable_value; + rb.len = buf_len; + rb.pos = 0; + rb.depth = 0; + rb.capture_suppression = 0; + rb.skip_decoded_value_capture = false; + + if (!zend_opcache_serializer_decode_zval(&rb, dst)) { + return false; + } + + return rb.pos == rb.len; +} + +static zend_always_inline bool zend_opcache_unserialize(zval *dst, const unsigned char *buf, size_t buf_len) +{ + return zend_opcache_unserialize_ex(dst, buf, buf_len, NULL, NULL); +} + +#endif diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c new file mode 100644 index 000000000000..bb8ce7f2f1de --- /dev/null +++ b/ext/opcache/zend_static_cache.c @@ -0,0 +1,1757 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" + +#include "Zend/zend_attributes.h" +#include "Zend/zend_atomic.h" +#include "Zend/zend_closures.h" +#include "Zend/zend_exceptions.h" + +#include "ZendAccelerator.h" +#include "zend_accelerator_module.h" +#include "zend_shared_alloc.h" +#include "zend_static_cache.h" +#include "zend_smart_str.h" + +#include "ext/date/php_date.h" +#include "ext/spl/spl_array.h" +#include "ext/spl/spl_fixedarray.h" +#include "ext/standard/php_var.h" + +#include "SAPI.h" + +#include "zend_static_cache_internal.h" +#include "zend_opcache_serializer.h" + +#include "opcache_arginfo.h" + +#define ZEND_OPCACHE_STATIC_CACHE_API_VALUE_TYPE "object|array|string|int|float|bool|null" + +zend_class_entry *zend_opcache_static_cache_exception_ce; +zend_class_entry *zend_opcache_static_cache_strategy_ce; + +static zend_class_entry *zend_opcache_static_cache_persistent_attribute_ce; +static zend_class_entry *zend_opcache_static_cache_volatile_static_attribute_ce; +static zend_class_entry *zend_opcache_static_cache_safe_direct_attribute_ce; + +zend_opcache_static_cache_context zend_opcache_static_cache_volatile_context_state = { + {0}, {0}, "volatile cache", "opcache_volatile_cache_lock", +#ifndef ZEND_WIN32 + ZEND_OPCACHE_STATIC_CACHE_VOLATILE_SEM_FILENAME_PREFIX, +#endif + true, false +}; + +zend_opcache_static_cache_context zend_opcache_static_cache_persistent_context_state = { + {0}, {0}, "persistent cache", "opcache_persistent_cache_lock", +#ifndef ZEND_WIN32 + ZEND_OPCACHE_STATIC_CACHE_PERSISTENT_SEM_FILENAME_PREFIX, +#endif + false, true +}; + +bool zend_opcache_static_cache_safe_direct_classes_marked = false; +bool zend_opcache_static_cache_subsystem_disabled = false; +const char *zend_opcache_static_cache_subsystem_failure_reason = NULL; + +static HashTable zend_opcache_static_cache_safe_direct_handler_table; +static bool zend_opcache_static_cache_safe_direct_handlers_initialized = false; + +#if defined(ZTS) && !defined(ZEND_WIN32) +ZEND_EXT_TLS bool zend_opcache_static_cache_zts_lock_is_write = false; +#endif +ZEND_EXT_TLS HashTable zend_opcache_static_cache_attribute_classes; +ZEND_EXT_TLS bool zend_opcache_static_cache_attribute_classes_initialized = false; +ZEND_EXT_TLS HashTable zend_opcache_static_cache_ignored_classes; +ZEND_EXT_TLS bool zend_opcache_static_cache_ignored_classes_initialized = false; +ZEND_EXT_TLS HashTable zend_opcache_static_cache_function_statics; +ZEND_EXT_TLS bool zend_opcache_static_cache_function_statics_initialized = false; +ZEND_EXT_TLS HashTable zend_opcache_static_cache_class_blob_handles; +ZEND_EXT_TLS bool zend_opcache_static_cache_class_blob_handles_initialized = false; +ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_volatile_runtime_state = {0}; +ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_persistent_runtime_state = {0}; +ZEND_EXT_TLS zend_opcache_static_cache_context *zend_opcache_static_cache_active_context_ptr = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_roots = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_references = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_arrays = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_objects = NULL; +ZEND_EXT_TLS bool zend_opcache_static_cache_has_tracked_arrays = false; +ZEND_EXT_TLS bool zend_opcache_static_cache_has_tracked_objects = false; +ZEND_EXT_TLS bool zend_opcache_static_cache_volatile_skip_publish = false; +ZEND_EXT_TLS bool zend_opcache_static_cache_persistent_skip_publish = false; +ZEND_EXT_TLS zend_opcache_static_cache_shared_graph_ref *zend_opcache_static_cache_shared_graph_refs = NULL; +ZEND_EXT_TLS uint32_t zend_opcache_static_cache_shared_graph_ref_count = 0; +ZEND_EXT_TLS uint32_t zend_opcache_static_cache_shared_graph_ref_capacity = 0; +ZEND_EXT_TLS zend_opcache_static_cache_shared_graph_ref *zend_opcache_static_cache_retired_shared_graphs = NULL; +ZEND_EXT_TLS uint32_t zend_opcache_static_cache_retired_shared_graph_count = 0; +ZEND_EXT_TLS uint32_t zend_opcache_static_cache_retired_shared_graph_capacity = 0; +/* Single-slot pointer caches that short-circuit the hash lookup performed by + * the mutation hooks. The caches are invalidated whenever the tracked-pointer + * map mutates. */ +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_last_array_ht = NULL; +ZEND_EXT_TLS zend_opcache_static_cache_tracked_dependency *zend_opcache_static_cache_last_array_dependency = NULL; +ZEND_EXT_TLS zend_object *zend_opcache_static_cache_last_object_obj = NULL; +ZEND_EXT_TLS zend_opcache_static_cache_tracked_dependency *zend_opcache_static_cache_last_object_dependency = NULL; +ZEND_EXT_TLS zend_opcache_static_cache_pending_mutation zend_opcache_static_cache_pending_mutation_state; +ZEND_EXT_TLS bool zend_opcache_static_cache_capture_active = false; +ZEND_EXT_TLS bool zend_opcache_static_cache_capture_available = false; +ZEND_EXT_TLS zend_opcache_static_cache_static_slot_handle *zend_opcache_static_cache_capture_handle = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_capture_arrays = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_capture_objects = NULL; +ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_volatile_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; +ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_persistent_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_volatile_request_local_slots = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_request_local_slots = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_volatile_entry_locks = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_entry_locks = NULL; +ZEND_EXT_TLS uint32_t zend_opcache_static_cache_volatile_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; +ZEND_EXT_TLS uint32_t zend_opcache_static_cache_persistent_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; + +static zend_always_inline bool zend_opcache_static_cache_validate_key(zend_string *key, uint32_t arg_num) +{ + if (ZSTR_LEN(key) == 0) { + zend_argument_value_error(arg_num, "must be a non-empty string"); + + return false; + } + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_validate_store_array_keys(HashTable *values, uint32_t arg_num) +{ + zend_string *key; + + ZEND_HASH_FOREACH_STR_KEY(values, key) { + if (key == NULL || ZSTR_LEN(key) == 0) { + zend_argument_value_error(arg_num, "must be an array with non-empty string keys"); + + return false; + } + } ZEND_HASH_FOREACH_END(); + + return true; +} + +static zend_always_inline void zend_opcache_static_cache_release_key_list(zend_string **keys, uint32_t key_count) +{ + uint32_t index; + + if (keys == NULL) { + return; + } + + for (index = 0; index < key_count; index++) { + zend_string_release(keys[index]); + } + + efree(keys); +} + +static zend_always_inline bool zend_opcache_static_cache_prepare_key_list( + HashTable *keys, + zend_string ***prepared_keys, + uint32_t *prepared_key_count, + uint32_t arg_num) +{ + zend_string **prepared; + zval *value; + uint32_t count, index = 0; + + ZEND_ASSERT(prepared_keys != NULL); + ZEND_ASSERT(prepared_key_count != NULL); + + count = zend_hash_num_elements(keys); + *prepared_keys = NULL; + *prepared_key_count = 0; + + if (count == 0) { + return true; + } + + prepared = safe_emalloc(count, sizeof(zend_string *), 0); + ZEND_HASH_FOREACH_VAL(keys, value) { + ZVAL_DEREF(value); + if (Z_TYPE_P(value) == IS_STRING) { + if (Z_STRLEN_P(value) == 0) { + zend_argument_value_error(arg_num, "must contain only non-empty string or int cache keys"); + zend_opcache_static_cache_release_key_list(prepared, index); + + return false; + } + + prepared[index++] = zend_string_copy(Z_STR_P(value)); + } else if (Z_TYPE_P(value) == IS_LONG) { + prepared[index++] = zend_long_to_str(Z_LVAL_P(value)); + } else { + zend_argument_value_error(arg_num, "must contain only non-empty string or int cache keys"); + zend_opcache_static_cache_release_key_list(prepared, index); + + return false; + } + } ZEND_HASH_FOREACH_END(); + + *prepared_keys = prepared; + *prepared_key_count = index; + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_validate_api_value(zval *value, uint32_t arg_num) +{ + ZVAL_DEREF(value); + if (Z_TYPE_P(value) == IS_RESOURCE) { + zend_argument_type_error(arg_num, "must be of type " ZEND_OPCACHE_STATIC_CACHE_API_VALUE_TYPE ", resource given"); + + return false; + } + + if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ce_closure) { + zend_argument_type_error(arg_num, "must not be a Closure object"); + + return false; + } + + return true; +} + +static void zend_opcache_static_cache_register_accelerator_handlers(void); + +static void zend_opcache_static_cache_safe_direct_handlers_dtor(zval *zv) +{ + pefree(Z_PTR_P(zv), true); +} + +void zend_opcache_static_cache_safe_direct_handlers_init(void) +{ + if (zend_opcache_static_cache_safe_direct_handlers_initialized) { + return; + } + + zend_hash_init( + &zend_opcache_static_cache_safe_direct_handler_table, + 8, + NULL, + zend_opcache_static_cache_safe_direct_handlers_dtor, + true + ); + zend_opcache_static_cache_safe_direct_handlers_initialized = true; +} + +void zend_opcache_static_cache_safe_direct_handlers_destroy(void) +{ + if (!zend_opcache_static_cache_safe_direct_handlers_initialized) { + return; + } + + zend_hash_destroy(&zend_opcache_static_cache_safe_direct_handler_table); + zend_opcache_static_cache_safe_direct_handlers_initialized = false; +} + +void zend_opcache_static_cache_safe_direct_register_class( + zend_class_entry *ce, + const zend_opcache_static_cache_safe_direct_handlers *handlers) +{ + zend_opcache_static_cache_safe_direct_handlers handlers_copy; + + if (ce == NULL || + zend_opcache_static_cache_safe_direct_attribute_ce == NULL || + handlers == NULL || + handlers->copy == NULL || + handlers->state_serialize == NULL || + handlers->state_unserialize == NULL + ) { + return; + } + + if (!zend_opcache_serializer_safe_direct_cache_has_attribute(ce->attributes)) { + zend_add_class_attribute(ce, zend_opcache_static_cache_safe_direct_attribute_ce->name, 0); + } + + zend_opcache_static_cache_safe_direct_handlers_init(); + handlers_copy = *handlers; + zend_hash_index_update_mem( + &zend_opcache_static_cache_safe_direct_handler_table, + (zend_ulong) (uintptr_t) ce, + &handlers_copy, + sizeof(handlers_copy) + ); +} + +static const zend_opcache_static_cache_safe_direct_handlers *zend_opcache_static_cache_safe_direct_find_handlers( + zend_class_entry *ce, + zend_class_entry **base_ce_ptr) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers; + + if (!zend_opcache_static_cache_safe_direct_handlers_initialized) { + return NULL; + } + + while (ce != NULL) { + handlers = zend_hash_index_find_ptr( + &zend_opcache_static_cache_safe_direct_handler_table, + (zend_ulong) (uintptr_t) ce + ); + if (handlers != NULL) { + if (base_ce_ptr != NULL) { + *base_ce_ptr = ce; + } + + return handlers; + } + + ce = ce->parent; + } + + return NULL; +} + +zend_opcache_static_cache_safe_direct_state_copy_func_t zend_opcache_static_cache_safe_direct_copy_func( + zend_class_entry *ce, + zend_class_entry **base_ce_ptr) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = + zend_opcache_static_cache_safe_direct_find_handlers(ce, base_ce_ptr); + + return handlers != NULL ? handlers->copy : NULL; +} + +zend_opcache_static_cache_safe_direct_state_has_unstorable_func_t zend_opcache_static_cache_safe_direct_state_has_unstorable_func( + zend_class_entry *ce) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = + zend_opcache_static_cache_safe_direct_find_handlers(ce, NULL); + + return handlers != NULL ? handlers->state_has_unstorable : NULL; +} + +zend_opcache_static_cache_safe_direct_state_serialize_func_t zend_opcache_static_cache_safe_direct_state_serialize_func( + zend_class_entry *ce) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = + zend_opcache_static_cache_safe_direct_find_handlers(ce, NULL); + + return handlers != NULL ? handlers->state_serialize : NULL; +} + +zend_opcache_static_cache_safe_direct_state_unserialize_func_t zend_opcache_static_cache_safe_direct_state_unserialize_func( + zend_class_entry *ce) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = + zend_opcache_static_cache_safe_direct_find_handlers(ce, NULL); + + return handlers != NULL ? handlers->state_unserialize : NULL; +} + +bool zend_opcache_static_cache_safe_direct_allows_custom_serializers(zend_class_entry *ce) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = + zend_opcache_static_cache_safe_direct_find_handlers(ce, NULL); + + return handlers != NULL && handlers->allows_custom_serializers; +} + +static zend_always_inline zend_string *zend_opcache_static_cache_validate_volatile_static_attribute( + zend_attribute *attr, + uint32_t target ZEND_ATTRIBUTE_UNUSED, + zend_class_entry *scope) +{ + zend_opcache_static_cache_volatile_static_attribute_config config; + zend_string *error = NULL; + + if (!zend_opcache_static_cache_volatile_static_attribute_config_from_attribute(attr, scope, &config, &error)) { + return error; + } + + return NULL; +} + +static zend_always_inline zend_string *zend_opcache_static_cache_validate_direct_cache_safe_attribute( + zend_attribute *attr ZEND_ATTRIBUTE_UNUSED, + uint32_t target, + zend_class_entry *scope) +{ + if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { + return ZSTR_INIT_LITERAL("Only classes can be marked with #[OPcache\\__DirectCacheSafe]", 0); + } + + if (scope == NULL || scope->type != ZEND_INTERNAL_CLASS) { + return ZSTR_INIT_LITERAL("Only internal classes can be marked with #[OPcache\\__DirectCacheSafe]", 0); + } + + return NULL; +} + +static zend_always_inline void zend_opcache_static_cache_register_classes(void) +{ + zend_internal_attribute *attribute; + + if (zend_opcache_static_cache_persistent_attribute_ce != NULL) { + return; + } + + zend_opcache_static_cache_persistent_attribute_ce = register_class_OPcache_PersistentStatic(); + zend_mark_internal_attribute(zend_opcache_static_cache_persistent_attribute_ce); + zend_opcache_static_cache_strategy_ce = register_class_OPcache_CacheStrategy(); + zend_opcache_static_cache_volatile_static_attribute_ce = register_class_OPcache_VolatileStatic(); + attribute = zend_mark_internal_attribute(zend_opcache_static_cache_volatile_static_attribute_ce); + attribute->validator = zend_opcache_static_cache_validate_volatile_static_attribute; + zend_opcache_static_cache_safe_direct_attribute_ce = register_class_OPcache___DirectCacheSafe(); + attribute = zend_mark_internal_attribute(zend_opcache_static_cache_safe_direct_attribute_ce); + attribute->validator = zend_opcache_static_cache_validate_direct_cache_safe_attribute; + zend_opcache_static_cache_exception_ce = register_class_OPcache_StaticCacheException(zend_ce_exception); +} + +static zend_always_inline void zend_opcache_static_cache_safe_direct_register_internal_classes(void) +{ + zend_class_entry *date_ce, *immutable_ce, *timezone_ce, *interval_ce, *fixedarray_ce, *arrayobject_ce, *arrayiterator_ce, *recursive_arrayiterator_ce; + const zend_opcache_static_cache_safe_direct_handlers *date_handlers, *spl_array_handlers, *spl_fixedarray_handlers; + + if (zend_opcache_static_cache_safe_direct_classes_marked) { + return; + } + + date_ce = php_date_get_date_ce(); + immutable_ce = php_date_get_immutable_ce(); + timezone_ce = php_date_get_timezone_ce(); + interval_ce = php_date_get_interval_ce(); + fixedarray_ce = spl_ce_SplFixedArray; + arrayobject_ce = spl_ce_ArrayObject; + arrayiterator_ce = spl_ce_ArrayIterator; + recursive_arrayiterator_ce = spl_ce_RecursiveArrayIterator; + if (date_ce == NULL || immutable_ce == NULL || timezone_ce == NULL || interval_ce == NULL || + fixedarray_ce == NULL || arrayobject_ce == NULL || arrayiterator_ce == NULL || + recursive_arrayiterator_ce == NULL + ) { + return; + } + + date_handlers = php_date_get_direct_cache_handlers(); + spl_fixedarray_handlers = spl_fixedarray_object_get_direct_cache_handlers(); + spl_array_handlers = spl_array_object_get_direct_cache_handlers(); + if (date_handlers == NULL || spl_fixedarray_handlers == NULL || spl_array_handlers == NULL) { + return; + } + + zend_opcache_static_cache_safe_direct_register_class(date_ce, date_handlers); + zend_opcache_static_cache_safe_direct_register_class(immutable_ce, date_handlers); + zend_opcache_static_cache_safe_direct_register_class(timezone_ce, date_handlers); + zend_opcache_static_cache_safe_direct_register_class(interval_ce, date_handlers); + zend_opcache_static_cache_safe_direct_register_class(fixedarray_ce, spl_fixedarray_handlers); + zend_opcache_static_cache_safe_direct_register_class(arrayobject_ce, spl_array_handlers); + zend_opcache_static_cache_safe_direct_register_class(arrayiterator_ce, spl_array_handlers); + zend_opcache_static_cache_safe_direct_register_class(recursive_arrayiterator_ce, spl_array_handlers); + + zend_opcache_static_cache_safe_direct_classes_marked = true; +} + +static zend_always_inline void zend_opcache_static_cache_invalidate_script_context(zend_opcache_static_cache_context *context, zend_persistent_script *persistent_script) +{ + zend_opcache_static_cache_context *previous_context; + + if (!zend_opcache_static_cache_context_runtime(context)->available || persistent_script == NULL) { + return; + } + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_wlock()) { + zend_opcache_static_cache_restore_context(previous_context); + return; + } + + if (zend_opcache_static_cache_header_init_locked()) { + zend_opcache_static_cache_delete_script_keys_locked(persistent_script); + } + + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_mark_publish_skipped(context); + zend_opcache_static_cache_restore_context(previous_context); +} + +static zend_always_inline void zend_opcache_static_cache_invalidate_all_context(zend_opcache_static_cache_context *context) +{ + zend_opcache_static_cache_context *previous_context; + + if (!zend_opcache_static_cache_context_runtime(context)->available) { + return; + } + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_wlock()) { + zend_opcache_static_cache_restore_context(previous_context); + + return; + } + + if (!zend_opcache_static_cache_header_init_locked()) { + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + + return; + } + + if (!zend_opcache_static_cache_clear_locked()) { + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + + return; + } + + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_release_active_entry_locks(); + zend_opcache_static_cache_mark_publish_skipped(context); + zend_opcache_static_cache_restore_context(previous_context); +} + +static zend_always_inline bool zend_opcache_static_cache_parse_ttl(zend_long ttl, uint32_t arg_num) +{ + if (ttl < 0) { + zend_argument_value_error(arg_num, "must be greater than or equal to 0"); + + return false; + } + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_require_available_read(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + + if (!zend_opcache_validate_api_restriction()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + + return false; + } + + if (!zend_opcache_static_cache_active_runtime()->available) { + if (zend_opcache_static_cache_active_runtime()->failure_reason) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", zend_opcache_static_cache_active_runtime()->failure_reason); + } else { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "OPcache %s is disabled", context->name); + } + + return false; + } + + if (!zend_opcache_static_cache_rlock()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s read lock", context->name); + + return false; + } + + if (!zend_opcache_static_cache_header_is_initialized_locked()) { + zend_opcache_static_cache_unlock(); + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to access the %s header", context->name); + + return false; + } + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_validate_available_write(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + + if (!zend_opcache_validate_api_restriction()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + + return false; + } + + if (!zend_opcache_static_cache_active_runtime()->available) { + if (zend_opcache_static_cache_active_runtime()->failure_reason) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", zend_opcache_static_cache_active_runtime()->failure_reason); + } else { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "OPcache %s is disabled", context->name); + } + + return false; + } + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_acquire_write_lock(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + + if (!zend_opcache_static_cache_wlock()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s lock", context->name); + + return false; + } + + if (!zend_opcache_static_cache_header_init_locked()) { + zend_opcache_static_cache_unlock(); + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to initialize the %s header", context->name); + + return false; + } + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_require_available_write(void) +{ + if (!zend_opcache_static_cache_validate_available_write()) { + return false; + } + + return zend_opcache_static_cache_acquire_write_lock(); +} + +static zend_always_inline bool zend_opcache_static_cache_begin_entry_mutation(zend_string *key, bool *release_entry_lock) +{ + ZEND_ASSERT(release_entry_lock != NULL); + *release_entry_lock = false; + + if (!zend_opcache_static_cache_has_entry_lock(key)) { + if (!zend_opcache_static_cache_acquire_entry_lock(key)) { + return false; + } + + *release_entry_lock = true; + } + + return true; +} + +static zend_always_inline void zend_opcache_static_cache_finish_entry_mutation(zend_string *key, bool release_entry_lock, bool successful_mutation) +{ + if (successful_mutation || release_entry_lock) { + zend_opcache_static_cache_release_entry_lock(key); + } +} + +static zend_always_inline bool zend_opcache_static_cache_explicit_delete_prevalidated(zend_string *key) +{ + bool deleted, release_entry_lock; + + release_entry_lock = zend_opcache_static_cache_has_entry_lock(key); + + if (!zend_opcache_static_cache_acquire_write_lock()) { + return false; + } + + deleted = zend_opcache_static_cache_delete_locked(key); + zend_opcache_static_cache_unlock(); + if (deleted && release_entry_lock) { + zend_opcache_static_cache_release_entry_lock(key); + } + + return deleted; +} + +static zend_always_inline bool zend_opcache_static_cache_explicit_clear_prevalidated(void) +{ + bool cleared; + + if (!zend_opcache_static_cache_acquire_write_lock()) { + return false; + } + + cleared = zend_opcache_static_cache_clear_locked(); + zend_opcache_static_cache_unlock(); + if (cleared) { + zend_opcache_static_cache_release_active_entry_locks(); + } + + return cleared; +} + +static zend_always_inline bool zend_opcache_static_cache_explicit_store_prevalidated(zend_string *key, zval *value, zend_long ttl, bool throw_on_failure) +{ + zend_opcache_static_cache_prepared_value prepared; + bool stored, release_entry_lock = false; + + if (!zend_opcache_static_cache_prepare_value(&prepared, key, value, throw_on_failure, false)) { + return false; + } + + if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock)) { + zend_opcache_static_cache_destroy_prepared_value(&prepared); + + return false; + } + + /* Callers validate availability before staging the value so large shared-graph + * builds stay outside the write lock. Once that preflight passed, the commit + * path only needs to acquire the lock and publish the prepared payload. */ + if (!zend_opcache_static_cache_acquire_write_lock()) { + zend_opcache_static_cache_destroy_prepared_value(&prepared); + if (release_entry_lock) { + zend_opcache_static_cache_release_entry_lock(key); + } + + return false; + } + + stored = zend_opcache_static_cache_store_prepared_locked(key, value, &prepared, ttl, throw_on_failure); + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_destroy_prepared_value(&prepared); + zend_opcache_static_cache_finish_entry_mutation(key, release_entry_lock, stored); + + return stored; +} + +static zend_always_inline zend_result zend_opcache_static_cache_fetch_api( + zend_opcache_static_cache_context *context, + zend_string *key, + zval *default_value, + zval *return_value) +{ + zend_opcache_static_cache_context *previous_context; + bool fetched, found = false; + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_require_available_read()) { + zend_opcache_static_cache_restore_context(previous_context); + + return FAILURE; + } + + fetched = zend_opcache_static_cache_fetch_locked(key, return_value, false, &found, true); + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + + if (!fetched) { + if (!found) { + ZVAL_COPY(return_value, default_value); + + return SUCCESS; + } + + if (!EG(exception)) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" could not be decoded", context->name, ZSTR_VAL(key)); + } + + return FAILURE; + } + + return SUCCESS; +} + +static zend_always_inline zend_result zend_opcache_static_cache_fetch_array_api( + zend_opcache_static_cache_context *context, + HashTable *keys, + zval *default_value, + zval *return_value) +{ + zend_opcache_static_cache_context *previous_context; + zend_string **prepared_keys, *key; + zval fetched_value; + uint32_t key_count, index; + bool fetched, found; + + if (!zend_opcache_static_cache_prepare_key_list(keys, &prepared_keys, &key_count, 1)) { + return FAILURE; + } + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_require_available_read()) { + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + + return FAILURE; + } + + array_init_size(return_value, key_count); + + for (index = 0; index < key_count; index++) { + key = prepared_keys[index]; + fetched = zend_opcache_static_cache_fetch_locked(key, &fetched_value, false, &found, false); + if (!fetched) { + if (!found) { + ZVAL_COPY(&fetched_value, default_value); + } else { + if (!EG(exception)) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" could not be decoded", context->name, ZSTR_VAL(key)); + } + zval_ptr_dtor(return_value); + ZVAL_UNDEF(return_value); + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + + return FAILURE; + } + } + + zend_hash_update(Z_ARRVAL_P(return_value), key, &fetched_value); + } + + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + + return SUCCESS; +} + +static zend_always_inline zend_result zend_opcache_static_cache_exists_api(zend_opcache_static_cache_context *context, zend_string *key, bool *exists) +{ + zend_opcache_static_cache_context *previous_context; + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_require_available_read()) { + zend_opcache_static_cache_restore_context(previous_context); + + return FAILURE; + } + + *exists = zend_opcache_static_cache_exists_locked(key); + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + + return SUCCESS; +} + +static zend_always_inline zend_result zend_opcache_static_cache_lock_api(zend_opcache_static_cache_context *context, zend_string *key, bool *locked) +{ + zend_opcache_static_cache_context *previous_context; + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_validate_available_write()) { + zend_opcache_static_cache_restore_context(previous_context); + + return FAILURE; + } + + *locked = zend_opcache_static_cache_try_acquire_entry_lock(key); + zend_opcache_static_cache_restore_context(previous_context); + + return SUCCESS; +} + +static zend_always_inline void zend_opcache_static_cache_disable_subsystem(const char *failure_reason) +{ + zend_opcache_static_cache_context *previous_context; + + zend_opcache_static_cache_subsystem_disabled = true; + zend_opcache_static_cache_subsystem_failure_reason = failure_reason != NULL + ? failure_reason + : "OPcache static cache startup failed" + ; + + zend_opcache_static_cache_unregister_hooks(); + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_shutdown_storage(); + zend_opcache_static_cache_reset_runtime(); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_shutdown_storage(); + zend_opcache_static_cache_reset_runtime(); + zend_opcache_static_cache_restore_context(previous_context); +} + +zend_result zend_opcache_register_functions(int module_type) +{ + return zend_register_functions(NULL, ext_functions, NULL, module_type); +} + +zend_result zend_opcache_static_cache_minit(void) +{ + zend_opcache_static_cache_context *previous_context; + + zend_opcache_static_cache_subsystem_disabled = false; + zend_opcache_static_cache_subsystem_failure_reason = NULL; + zend_opcache_static_cache_safe_direct_classes_marked = false; + + zend_opcache_static_cache_register_classes(); + zend_opcache_static_cache_safe_direct_handlers_init(); + zend_opcache_static_cache_register_accelerator_handlers(); + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_reset_storage(); + + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_reset_storage(); + + zend_opcache_static_cache_restore_context(previous_context); + + return SUCCESS; +} + +static void zend_opcache_static_cache_startup(void) +{ + const char *failure_reason; + zend_opcache_static_cache_context *previous_context; + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_reset_runtime(); + if (zend_opcache_static_cache_active_runtime()->enabled && ZCG(enabled) && accel_startup_ok && !file_cache_only) { + if (!zend_opcache_static_cache_startup_storage_before_request()) { + failure_reason = zend_opcache_static_cache_active_runtime()->failure_reason; + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_disable_subsystem(failure_reason); + + return; + } + } + + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_reset_runtime(); + if (zend_opcache_static_cache_active_runtime()->enabled && ZCG(enabled) && accel_startup_ok && !file_cache_only) { + if (!zend_opcache_static_cache_startup_storage_before_request()) { + failure_reason = zend_opcache_static_cache_active_runtime()->failure_reason; + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_disable_subsystem(failure_reason); + + return; + } + } + + zend_opcache_static_cache_restore_context(previous_context); + + if (!zend_opcache_static_cache_subsystem_disabled && + ( + ZCG(accel_directives).static_cache_volatile_size_mb != 0 || + ZCG(accel_directives).static_cache_persistent_size_mb != 0 + ) + ) { + zend_opcache_static_cache_register_hooks(); + } +} + +static void zend_opcache_static_cache_post_startup(void) +{ + const char *failure_reason; + zend_opcache_static_cache_context *previous_context; + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_reset_runtime(); + if (!(zend_opcache_static_cache_active_context()->storage).initialized && + zend_opcache_static_cache_active_runtime()->enabled && + ZCG(enabled) && + accel_startup_ok && + !file_cache_only + ) { + if (!zend_opcache_static_cache_startup_storage_before_request()) { + failure_reason = zend_opcache_static_cache_active_runtime()->failure_reason; + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_disable_subsystem(failure_reason); + + return; + } + } + + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_reset_runtime(); + if (!(zend_opcache_static_cache_active_context()->storage).initialized && + zend_opcache_static_cache_active_runtime()->enabled && + ZCG(enabled) && + accel_startup_ok && + !file_cache_only + ) { + if (!zend_opcache_static_cache_startup_storage_before_request()) { + failure_reason = zend_opcache_static_cache_active_runtime()->failure_reason; + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_disable_subsystem(failure_reason); + + return; + } + } + + zend_opcache_static_cache_restore_context(previous_context); + + if (!zend_opcache_static_cache_subsystem_disabled && + ( + ZCG(accel_directives).static_cache_volatile_size_mb != 0 || + ZCG(accel_directives).static_cache_persistent_size_mb != 0 + ) + ) { + zend_opcache_static_cache_safe_direct_register_internal_classes(); + } +} + +void zend_opcache_static_cache_mshutdown(void) +{ + zend_opcache_static_cache_context *previous_context; + + zend_opcache_static_cache_unregister_hooks(); + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_shutdown_storage(); + zend_opcache_static_cache_reset_runtime(); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_shutdown_storage(); + zend_opcache_static_cache_reset_runtime(); + zend_opcache_static_cache_restore_context(previous_context); + + zend_opcache_static_cache_subsystem_disabled = false; + zend_opcache_static_cache_subsystem_failure_reason = NULL; + zend_opcache_static_cache_safe_direct_classes_marked = false; + zend_opcache_static_cache_safe_direct_handlers_destroy(); + zend_accel_register_static_cache_handlers(NULL); +} + +static zend_result zend_opcache_static_cache_rinit(void) +{ + zend_opcache_static_cache_context *previous_context; + bool static_cache_available; + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_ensure_ready(); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_ensure_ready(); + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_clear_lookup_caches(); + zend_opcache_static_cache_reset_publish_skips(); + + EG(static_cache_class_access_active) = false; + EG(tracked_mutation_hooks_active) = false; + + static_cache_available = zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_volatile_context_state)->available || + zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_persistent_context_state)->available + ; + if (!static_cache_available) { + return SUCCESS; + } + + zend_opcache_static_cache_register_hooks(); + + EG(static_cache_class_access_active) = true; + zend_opcache_static_cache_request_init(); + + return SUCCESS; +} + +zend_result zend_opcache_static_cache_rshutdown(void) +{ + zend_opcache_static_cache_clear_lookup_caches(); + zend_opcache_static_cache_request_shutdown(); + zend_opcache_static_cache_release_request_entry_locks(); + zend_opcache_static_cache_release_request_local_slots(); + zend_opcache_static_cache_release_request_shared_graph_refs(); + + EG(static_cache_class_access_active) = false; + EG(tracked_mutation_hooks_active) = false; + + return SUCCESS; +} + +static void zend_opcache_static_cache_invalidate_script(zend_persistent_script *persistent_script) +{ + zend_opcache_static_cache_invalidate_script_context(&zend_opcache_static_cache_persistent_context_state, persistent_script); + zend_opcache_static_cache_invalidate_script_context(&zend_opcache_static_cache_volatile_context_state, persistent_script); +} + +static void zend_opcache_static_cache_register_accelerator_handlers(void) +{ + static const zend_opcache_static_cache_accelerator_handlers handlers = { + zend_opcache_static_cache_startup, + zend_opcache_static_cache_post_startup, + zend_opcache_static_cache_rinit, + zend_opcache_static_cache_invalidate_script + }; + + zend_accel_register_static_cache_handlers(&handlers); +} + +void zend_opcache_static_cache_invalidate_all(void) +{ + zend_opcache_static_cache_invalidate_all_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_invalidate_all_context(&zend_opcache_static_cache_volatile_context_state); +} + +void zend_opcache_static_cache_volatile_get_status(zval *return_value) +{ + zend_opcache_static_cache_context *previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + + zend_opcache_static_cache_populate_array(return_value); + zend_opcache_static_cache_restore_context(previous_context); +} + +void zend_opcache_static_cache_persistent_get_status(zval *return_value) +{ + zend_opcache_static_cache_context *previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + + zend_opcache_static_cache_populate_array(return_value); + zend_opcache_static_cache_restore_context(previous_context); +} + +bool zend_opcache_static_cache_volatile_is_enabled(void) +{ + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_volatile_context_state)->enabled; +} + +bool zend_opcache_static_cache_volatile_is_available(void) +{ + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_volatile_context_state)->available; +} + +const char *zend_opcache_static_cache_volatile_failure_reason(void) +{ + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_volatile_context_state)->failure_reason; +} + +bool zend_opcache_static_cache_persistent_is_enabled(void) +{ + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_persistent_context_state)->enabled; +} + +bool zend_opcache_static_cache_persistent_is_available(void) +{ + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_persistent_context_state)->available; +} + +const char *zend_opcache_static_cache_persistent_failure_reason(void) +{ + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_persistent_context_state)->failure_reason; +} + +ZEND_METHOD(OPcache_VolatileStatic, __construct) +{ + zend_long ttl = 0; + zval default_strategy, *strategy = NULL; + + ZVAL_UNDEF(&default_strategy); + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(ttl) + Z_PARAM_OBJECT_OF_CLASS(strategy, zend_opcache_static_cache_strategy_ce) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_parse_ttl(ttl, 1)) { + RETURN_THROWS(); + } + + if (strategy == NULL) { + ZVAL_OBJ_COPY(&default_strategy, zend_enum_get_case_cstr(zend_opcache_static_cache_strategy_ce, "Immediate")); + strategy = &default_strategy; + } + + zend_update_property_long(zend_opcache_static_cache_volatile_static_attribute_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("ttl"), ttl); + zend_update_property(zend_opcache_static_cache_volatile_static_attribute_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("strategy"), strategy); + zval_ptr_dtor(&default_strategy); +} + +ZEND_FUNCTION(OPcache_volatile_store) +{ + zend_string *key; + zend_long ttl = 0; + zval *value; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(key) + Z_PARAM_ZVAL(value) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(ttl) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_validate_api_value(value, 2)) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_parse_ttl(ttl, 3) || !zend_opcache_static_cache_validate_available_write()) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, false)) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; + } + + RETURN_TRUE; +} + +ZEND_FUNCTION(OPcache_volatile_store_array) +{ + zend_string *key; + zend_long ttl = 0; + zval *value; + HashTable *values; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ARRAY_HT(values) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(ttl) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_parse_ttl(ttl, 2) || !zend_opcache_static_cache_validate_available_write()) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_validate_store_array_keys(values, 1)) { + RETURN_THROWS(); + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(values, key, value) { + ZEND_ASSERT(key != NULL); + + if (!zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, false)) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; + } + } ZEND_HASH_FOREACH_END(); + + RETURN_TRUE; +} + +ZEND_FUNCTION(OPcache_volatile_fetch) +{ + zend_string *key; + zval *default_value = NULL, default_null; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(default_value) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (default_value == NULL) { + ZVAL_NULL(&default_null); + default_value = &default_null; + } + + if (!zend_opcache_static_cache_validate_api_value(default_value, 2)) { + RETURN_THROWS(); + } + + if (zend_opcache_static_cache_fetch_api(&zend_opcache_static_cache_volatile_context_state, key, default_value, return_value) == FAILURE) { + RETURN_THROWS(); + } +} + +ZEND_FUNCTION(OPcache_volatile_fetch_array) +{ + HashTable *keys; + zval *default_value = NULL, default_null; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ARRAY_HT(keys) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(default_value) + ZEND_PARSE_PARAMETERS_END(); + + if (default_value == NULL) { + ZVAL_NULL(&default_null); + default_value = &default_null; + } + + if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_volatile_context_state, keys, default_value, return_value) == FAILURE) { + RETURN_THROWS(); + } +} + +ZEND_FUNCTION(OPcache_volatile_exists) +{ + zend_string *key; + bool exists; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (zend_opcache_static_cache_exists_api(&zend_opcache_static_cache_volatile_context_state, key, &exists) == FAILURE) { + RETURN_THROWS(); + } + + RETURN_BOOL(exists); +} + +ZEND_FUNCTION(OPcache_volatile_lock) +{ + zend_string *key; + bool locked; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_volatile_context_state, key, &locked) == FAILURE) { + RETURN_THROWS(); + } + + RETURN_BOOL(locked); +} + +ZEND_FUNCTION(OPcache_volatile_delete) +{ + zend_string *key; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_validate_available_write()) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_explicit_delete_prevalidated(key)) { + RETURN_THROWS(); + } +} + +ZEND_FUNCTION(OPcache_volatile_delete_array) +{ + zend_string **prepared_keys; + HashTable *keys; + uint32_t key_count, index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(keys) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_prepare_key_list(keys, &prepared_keys, &key_count, 1)) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_validate_available_write()) { + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + + RETURN_THROWS(); + } + + for (index = 0; index < key_count; index++) { + if (!zend_opcache_static_cache_explicit_delete_prevalidated(prepared_keys[index])) { + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + + RETURN_THROWS(); + } + } + + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); +} + +ZEND_FUNCTION(OPcache_volatile_clear) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + if (!zend_opcache_static_cache_validate_available_write()) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_explicit_clear_prevalidated()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the volatile cache"); + + RETURN_THROWS(); + } + + zend_opcache_static_cache_mark_publish_skipped(&zend_opcache_static_cache_volatile_context_state); +} + +ZEND_FUNCTION(OPcache_volatile_cache_info) +{ + zend_opcache_static_cache_context *previous_context; + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!zend_opcache_validate_api_restriction()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + + RETURN_THROWS(); + } + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_populate_array(return_value); + zend_opcache_static_cache_restore_context(previous_context); +} + +ZEND_FUNCTION(OPcache_persistent_store) +{ + zend_opcache_static_cache_context *previous_context; + zend_string *key; + zval *value; + bool stored; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(key) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_validate_api_value(value, 2)) { + RETURN_THROWS(); + } + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + if (!zend_opcache_static_cache_validate_available_write()) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, true); + zend_opcache_static_cache_restore_context(previous_context); + + if (!stored) { + RETURN_THROWS(); + } +} + +ZEND_FUNCTION(OPcache_persistent_store_array) +{ + zend_opcache_static_cache_context *previous_context; + zend_string *key; + zval *value; + HashTable *values; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(values) + ZEND_PARSE_PARAMETERS_END(); + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + if (!zend_opcache_static_cache_validate_available_write()) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_validate_store_array_keys(values, 1)) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(values, key, value) { + ZEND_ASSERT(key != NULL); + + if (!zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, true)) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + } ZEND_HASH_FOREACH_END(); + + zend_opcache_static_cache_restore_context(previous_context); +} + +ZEND_FUNCTION(OPcache_persistent_fetch) +{ + zend_string *key; + zval *default_value = NULL, default_null; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(default_value) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (default_value == NULL) { + ZVAL_NULL(&default_null); + default_value = &default_null; + } + + if (!zend_opcache_static_cache_validate_api_value(default_value, 2)) { + RETURN_THROWS(); + } + + if (zend_opcache_static_cache_fetch_api(&zend_opcache_static_cache_persistent_context_state, key, default_value, return_value) == FAILURE) { + RETURN_THROWS(); + } +} + +ZEND_FUNCTION(OPcache_persistent_fetch_array) +{ + HashTable *keys; + zval *default_value = NULL, default_null; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ARRAY_HT(keys) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_NULL(default_value) + ZEND_PARSE_PARAMETERS_END(); + + if (default_value == NULL) { + ZVAL_NULL(&default_null); + default_value = &default_null; + } + + if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_persistent_context_state, keys, default_value, return_value) == FAILURE) { + RETURN_THROWS(); + } +} + +ZEND_FUNCTION(OPcache_persistent_exists) +{ + zend_string *key; + bool exists; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (zend_opcache_static_cache_exists_api(&zend_opcache_static_cache_persistent_context_state, key, &exists) == FAILURE) { + RETURN_THROWS(); + } + + RETURN_BOOL(exists); +} + +ZEND_FUNCTION(OPcache_persistent_lock) +{ + zend_string *key; + bool locked; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_persistent_context_state, key, &locked) == FAILURE) { + RETURN_THROWS(); + } + + RETURN_BOOL(locked); +} + +ZEND_FUNCTION(OPcache_persistent_delete) +{ + zend_opcache_static_cache_context *previous_context; + zend_string *key; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + if (!zend_opcache_static_cache_validate_available_write()) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_explicit_delete_prevalidated(key)) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + zend_opcache_static_cache_restore_context(previous_context); +} + +ZEND_FUNCTION(OPcache_persistent_delete_array) +{ + zend_opcache_static_cache_context *previous_context; + zend_string **prepared_keys; + HashTable *keys; + uint32_t key_count, index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(keys) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_prepare_key_list(keys, &prepared_keys, &key_count, 1)) { + RETURN_THROWS(); + } + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + if (!zend_opcache_static_cache_validate_available_write()) { + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + + RETURN_THROWS(); + } + + for (index = 0; index < key_count; index++) { + if (!zend_opcache_static_cache_explicit_delete_prevalidated(prepared_keys[index])) { + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + + RETURN_THROWS(); + } + } + + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_release_key_list(prepared_keys, key_count); +} + +ZEND_FUNCTION(OPcache_persistent_clear) +{ + zend_opcache_static_cache_context *previous_context; + + ZEND_PARSE_PARAMETERS_NONE(); + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + if (!zend_opcache_static_cache_validate_available_write()) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_explicit_clear_prevalidated()) { + zend_opcache_static_cache_restore_context(previous_context); + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the persistent cache"); + + RETURN_THROWS(); + } + + zend_opcache_static_cache_mark_publish_skipped(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_restore_context(previous_context); +} + +ZEND_FUNCTION(OPcache_persistent_atomic_increment) +{ + zend_opcache_static_cache_context *previous_context; + zend_long step = 1, new_value; + zend_string *key; + bool release_entry_lock = false; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(step) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock)) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_require_available_write()) { + if (release_entry_lock) { + zend_opcache_static_cache_release_entry_lock(key); + } + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_atomic_update_locked(key, step, false, true, &new_value, "Atomic increment requires an integer value")) { + zend_opcache_static_cache_unlock(); + if (release_entry_lock) { + zend_opcache_static_cache_release_entry_lock(key); + } + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + RETVAL_LONG(new_value); + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_finish_entry_mutation(key, release_entry_lock, true); + zend_opcache_static_cache_restore_context(previous_context); +} + +ZEND_FUNCTION(OPcache_persistent_atomic_decrement) +{ + zend_opcache_static_cache_context *previous_context; + zend_string *key; + zend_long step = 1, new_value; + bool release_entry_lock = false; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(step) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key(key, 1)) { + RETURN_THROWS(); + } + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock)) { + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_require_available_write()) { + if (release_entry_lock) { + zend_opcache_static_cache_release_entry_lock(key); + } + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + if (!zend_opcache_static_cache_atomic_update_locked(key, step, true, false, &new_value, "Atomic decrement requires an integer value")) { + zend_opcache_static_cache_unlock(); + if (release_entry_lock) { + zend_opcache_static_cache_release_entry_lock(key); + } + zend_opcache_static_cache_restore_context(previous_context); + + RETURN_THROWS(); + } + + RETVAL_LONG(new_value); + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_finish_entry_mutation(key, release_entry_lock, true); + zend_opcache_static_cache_restore_context(previous_context); +} + +ZEND_FUNCTION(OPcache_persistent_cache_info) +{ + zend_opcache_static_cache_context *previous_context; + + ZEND_PARSE_PARAMETERS_NONE(); + + if (!zend_opcache_validate_api_restriction()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + + RETURN_THROWS(); + } + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_populate_array(return_value); + zend_opcache_static_cache_restore_context(previous_context); +} diff --git a/ext/opcache/zend_static_cache.h b/ext/opcache/zend_static_cache.h new file mode 100644 index 000000000000..d50f5c57e7ca --- /dev/null +++ b/ext/opcache/zend_static_cache.h @@ -0,0 +1,90 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_STATIC_CACHE_H +#define ZEND_STATIC_CACHE_H + +#include "php.h" + +typedef bool (*zend_opcache_static_cache_safe_direct_clone_value_func_t)( + void *context, + zval *dst, + zval *src +); + +typedef bool (*zend_opcache_static_cache_safe_direct_value_has_unstorable_func_t)( + void *context, + const zval *value +); + +typedef bool (*zend_opcache_static_cache_safe_direct_state_copy_func_t)( + void *context, + zend_object *old_object, + zend_object *new_object, + zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value +); + +typedef bool (*zend_opcache_static_cache_safe_direct_state_has_unstorable_func_t)( + void *context, + const zval *value, + zend_opcache_static_cache_safe_direct_value_has_unstorable_func_t value_has_unstorable +); + +typedef bool (*zend_opcache_static_cache_safe_direct_state_serialize_func_t)( + const zval *object, + zval *state +); + +typedef bool (*zend_opcache_static_cache_safe_direct_state_unserialize_func_t)( + zval *object, + zval *state +); + +#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; +#endif + +#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_DEFINED +# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_DEFINED +struct _zend_opcache_static_cache_safe_direct_handlers { + bool allows_custom_serializers; + zend_opcache_static_cache_safe_direct_state_copy_func_t copy; + zend_opcache_static_cache_safe_direct_state_has_unstorable_func_t state_has_unstorable; + zend_opcache_static_cache_safe_direct_state_serialize_func_t state_serialize; + zend_opcache_static_cache_safe_direct_state_unserialize_func_t state_unserialize; +}; +#endif + +BEGIN_EXTERN_C() + +zend_result zend_opcache_register_functions(int module_type); +zend_result zend_opcache_static_cache_minit(void); +void zend_opcache_static_cache_mshutdown(void); +zend_result zend_opcache_static_cache_rshutdown(void); +void zend_opcache_static_cache_invalidate_all(void); +void zend_opcache_static_cache_volatile_get_status(zval *return_value); +void zend_opcache_static_cache_persistent_get_status(zval *return_value); +bool zend_opcache_static_cache_volatile_is_enabled(void); +bool zend_opcache_static_cache_volatile_is_available(void); +const char *zend_opcache_static_cache_volatile_failure_reason(void); +bool zend_opcache_static_cache_persistent_is_enabled(void); +bool zend_opcache_static_cache_persistent_is_available(void); +const char *zend_opcache_static_cache_persistent_failure_reason(void); + +END_EXTERN_C() + +#endif /* ZEND_STATIC_CACHE_H */ diff --git a/ext/opcache/zend_static_cache_entries.c b/ext/opcache/zend_static_cache_entries.c new file mode 100644 index 000000000000..743938da518a --- /dev/null +++ b/ext/opcache/zend_static_cache_entries.c @@ -0,0 +1,2038 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + +#include "zend_static_cache_internal.h" +#include "zend_opcache_serializer.h" + +#include "Zend/zend_closures.h" +#include "Zend/zend_objects.h" + +typedef struct _zend_opcache_static_cache_request_local_clone_context { + HashTable arrays; + HashTable objects; + HashTable references; +} zend_opcache_static_cache_request_local_clone_context; + +/* Value graph cloning is mutually recursive across arrays, references, and objects. */ +static bool zend_opcache_static_cache_clone_request_local_value( + zend_opcache_static_cache_request_local_clone_context *context, + zval *dst, + zval *src +); + +static zend_always_inline void zend_opcache_static_cache_release_value_storage_locked(uint8_t value_type, uint32_t value_offset) +{ + if (value_offset == 0) { + return; + } + + if (value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { + if (zend_opcache_static_cache_shared_graph_retire_payload_locked(value_offset)) { + zend_opcache_static_cache_free_locked(value_offset); + } + } else if (zend_opcache_static_cache_value_uses_offset(value_type)) { + zend_opcache_static_cache_free_locked(value_offset); + } +} + +static zend_always_inline void zend_opcache_static_cache_release_entry_storage_locked(zend_opcache_static_cache_entry *entry) +{ + bool uses_combined_value_key = (entry->reserved & ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY) != 0; + + if (entry->key_offset != 0 && !uses_combined_value_key) { + zend_opcache_static_cache_free_locked(entry->key_offset); + } + + zend_opcache_static_cache_release_value_storage_locked(entry->value_type, entry->value_offset); +} + +static zend_always_inline void zend_opcache_static_cache_delete_entry_locked(zend_opcache_static_cache_entry *entry, zend_opcache_static_cache_header *header) +{ + if (entry->state == ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED && header->count != 0) { + header->count--; + } + + zend_opcache_static_cache_release_entry_storage_locked(entry); + entry->hash = 0; + entry->key_offset = 0; + entry->key_len = 0; + entry->state = ZEND_OPCACHE_STATIC_CACHE_ENTRY_TOMBSTONE; + entry->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL; + entry->value_offset = 0; + entry->value_len = 0; + entry->reserved = 0; + entry->expires_at = 0; + entry->long_value = 0; + entry->double_value = 0; + zend_opcache_static_cache_bump_mutation_epoch_locked(header); +} + +static void zend_opcache_static_cache_request_local_slot_dtor(zval *slot_zv) +{ + zend_opcache_static_cache_request_local_slot *slot = Z_PTR_P(slot_zv); + + if (!Z_ISUNDEF(slot->value)) { + zval_ptr_dtor(&slot->value); + } + efree(slot); +} + +static bool zend_opcache_static_cache_value_needs_request_local_clone_inner( + zval *value, + HashTable *visited_arrays) +{ + zend_ulong array_key; + zval *element; + + if (Z_ISREF_P(value)) { + return true; + } + + switch (Z_TYPE_P(value)) { + case IS_OBJECT: + return true; + case IS_ARRAY: + array_key = (zend_ulong) (uintptr_t) Z_ARRVAL_P(value); + if (zend_hash_index_exists(visited_arrays, array_key)) { + return false; + } + + zend_hash_index_add_empty_element(visited_arrays, array_key); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value), element) { + if (zend_opcache_static_cache_value_needs_request_local_clone_inner(element, visited_arrays)) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; + default: + return false; + } +} + +static bool zend_opcache_static_cache_value_needs_request_local_clone(zval *value) +{ + HashTable visited_arrays; + bool result; + + if (Z_ISREF_P(value)) { + return true; + } + if (Z_TYPE_P(value) == IS_OBJECT) { + return true; + } + if (Z_TYPE_P(value) != IS_ARRAY) { + return false; + } + + zend_hash_init(&visited_arrays, 8, NULL, NULL, 0); + result = zend_opcache_static_cache_value_needs_request_local_clone_inner(value, &visited_arrays); + zend_hash_destroy(&visited_arrays); + + return result; +} + +static void zend_opcache_static_cache_request_local_clone_object_dtor(zval *object_zv) +{ + zend_object *object = Z_PTR_P(object_zv); + + OBJ_RELEASE(object); +} + +static void zend_opcache_static_cache_request_local_clone_array_dtor(zval *array_zv) +{ + zend_array *array = Z_PTR_P(array_zv); + + zend_array_release(array); +} + +static void zend_opcache_static_cache_request_local_clone_reference_dtor(zval *reference_zv) +{ + zval ref_zv; + + ZVAL_REF(&ref_zv, (zend_reference *) Z_PTR_P(reference_zv)); + zval_ptr_dtor(&ref_zv); +} + +static void zend_opcache_static_cache_request_local_clone_context_init( + zend_opcache_static_cache_request_local_clone_context *context) +{ + zend_hash_init(&context->arrays, 8, NULL, zend_opcache_static_cache_request_local_clone_array_dtor, 0); + zend_hash_init(&context->objects, 8, NULL, zend_opcache_static_cache_request_local_clone_object_dtor, 0); + zend_hash_init(&context->references, 8, NULL, zend_opcache_static_cache_request_local_clone_reference_dtor, 0); +} + +static void zend_opcache_static_cache_request_local_clone_context_destroy( + zend_opcache_static_cache_request_local_clone_context *context) +{ + zend_hash_destroy(&context->references); + zend_hash_destroy(&context->objects); + zend_hash_destroy(&context->arrays); +} + +static bool zend_opcache_static_cache_clone_request_local_array_ex( + zend_opcache_static_cache_request_local_clone_context *context, + zval *dst, + zval *src, + bool known_needs_clone) +{ + zend_ulong key = (zend_ulong) (uintptr_t) Z_ARRVAL_P(src); + zend_array *array = zend_hash_index_find_ptr(&context->arrays, key); + zval *element, cloned_element; + + if (!known_needs_clone && !zend_opcache_static_cache_value_needs_request_local_clone(src)) { + ZVAL_COPY(dst, src); + + return true; + } + + if (array != NULL) { + GC_ADDREF(array); + ZVAL_ARR(dst, array); + + return true; + } + + array = zend_array_dup(Z_ARRVAL_P(src)); + zend_hash_index_update_ptr(&context->arrays, key, array); + GC_ADDREF(array); + ZVAL_ARR(dst, array); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(dst), element) { + if (Z_TYPE_P(element) == IS_INDIRECT || + !zend_opcache_static_cache_value_needs_request_local_clone(element) + ) { + continue; + } + + if (!zend_opcache_static_cache_clone_request_local_value(context, &cloned_element, element)) { + zval_ptr_dtor(dst); + ZVAL_UNDEF(dst); + + return false; + } + + zval_ptr_dtor(element); + ZVAL_COPY_VALUE(element, &cloned_element); + } ZEND_HASH_FOREACH_END(); + + return true; +} + +static bool zend_opcache_static_cache_clone_request_local_array( + zend_opcache_static_cache_request_local_clone_context *context, + zval *dst, + zval *src) +{ + return zend_opcache_static_cache_clone_request_local_array_ex(context, dst, src, false); +} + +static bool zend_opcache_static_cache_clone_request_local_reference( + zend_opcache_static_cache_request_local_clone_context *context, + zval *dst, + zend_reference *src_ref) +{ + zend_ulong key = (zend_ulong) (uintptr_t) src_ref; + zend_reference *new_ref = zend_hash_index_find_ptr(&context->references, key); + zval inner; + + if (new_ref != NULL) { + GC_ADDREF(new_ref); + ZVAL_REF(dst, new_ref); + + return true; + } + + ZVAL_NEW_EMPTY_REF(dst); + new_ref = Z_REF_P(dst); + ZVAL_UNDEF(&new_ref->val); + zend_hash_index_update_ptr(&context->references, key, new_ref); + + if (!zend_opcache_static_cache_clone_request_local_value(context, &inner, &src_ref->val)) { + ZVAL_UNDEF(dst); + + return false; + } + + ZVAL_COPY_VALUE(&new_ref->val, &inner); + GC_ADDREF(new_ref); + ZVAL_REF(dst, new_ref); + + return true; +} + +static bool zend_opcache_static_cache_clone_request_local_object_members( + zend_opcache_static_cache_request_local_clone_context *context, + zend_object *old_object, + zend_object *new_object) +{ + zend_ulong num_key; + zend_string *key; + zval *src, *dst, *end, *prop, new_prop; + + if (old_object->ce->default_properties_count) { + src = old_object->properties_table; + dst = new_object->properties_table; + end = src + old_object->ce->default_properties_count; + do { + if (!zend_opcache_static_cache_clone_request_local_value(context, &new_prop, src)) { + return false; + } + + zval_ptr_dtor(dst); + ZVAL_COPY_VALUE(dst, &new_prop); + Z_PROP_FLAG_P(dst) = Z_PROP_FLAG_P(src); + src++; + dst++; + } while (src != end); + } + + if (old_object->properties != NULL && zend_hash_num_elements(old_object->properties) != 0) { + if (new_object->properties != NULL) { + zend_hash_clean(new_object->properties); + zend_hash_extend(new_object->properties, zend_hash_num_elements(old_object->properties), 0); + } else { + new_object->properties = zend_new_array(zend_hash_num_elements(old_object->properties)); + zend_hash_real_init_mixed(new_object->properties); + } + + HT_FLAGS(new_object->properties) |= + HT_FLAGS(old_object->properties) & HASH_FLAG_HAS_EMPTY_IND; + + ZEND_HASH_MAP_FOREACH_KEY_VAL(old_object->properties, num_key, key, prop) { + if (Z_TYPE_P(prop) == IS_INDIRECT) { + ZVAL_INDIRECT( + &new_prop, + new_object->properties_table + (Z_INDIRECT_P(prop) - old_object->properties_table) + ); + } else if (!zend_opcache_static_cache_clone_request_local_value(context, &new_prop, prop)) { + return false; + } + + if (key != NULL) { + _zend_hash_append(new_object->properties, key, &new_prop); + } else { + zend_hash_index_add_new(new_object->properties, num_key, &new_prop); + } + } ZEND_HASH_FOREACH_END(); + } + + return true; +} + +static bool zend_opcache_static_cache_clone_request_local_std_object( + zend_opcache_static_cache_request_local_clone_context *context, + zend_object *old_object, + zend_object **new_object_ptr) +{ + zend_object *new_object; + zval *dst, *end; + + new_object = zend_objects_new(old_object->ce); + + if (new_object->ce->default_properties_count) { + dst = new_object->properties_table; + end = dst + new_object->ce->default_properties_count; + do { + ZVAL_UNDEF(dst); + dst++; + } while (dst != end); + } + + zend_hash_index_update_ptr( + &context->objects, + (zend_ulong) (uintptr_t) old_object, + new_object + ); + + if (!zend_opcache_static_cache_clone_request_local_object_members(context, old_object, new_object)) { + return false; + } + + *new_object_ptr = new_object; + + return true; +} + +static bool zend_opcache_static_cache_clone_request_local_value_callback( + void *context, + zval *dst, + zval *src) +{ + return zend_opcache_static_cache_clone_request_local_value( + (zend_opcache_static_cache_request_local_clone_context *) context, + dst, + src + ); +} + +static bool zend_opcache_static_cache_clone_request_local_safe_direct_object( + zend_opcache_static_cache_request_local_clone_context *context, + zend_object *old_object, + zend_object **new_object_ptr) +{ + zend_class_entry *ce = old_object->ce, *base_ce = NULL; + zend_opcache_static_cache_safe_direct_state_copy_func_t copy_func; + zend_object *new_object; + zend_ulong key; + zval new_zv; + + copy_func = zend_opcache_static_cache_safe_direct_copy_func(ce, &base_ce); + if (copy_func == NULL || zend_opcache_serializer_has_safe_direct_cache_overrides(ce, base_ce)) { + return false; + } + + ZVAL_UNDEF(&new_zv); + if (object_init_ex(&new_zv, ce) != SUCCESS) { + return false; + } + + new_object = Z_OBJ(new_zv); + key = (zend_ulong) (uintptr_t) old_object; + zend_hash_index_update_ptr(&context->objects, key, new_object); + + if (!copy_func( + context, + old_object, + new_object, + zend_opcache_static_cache_clone_request_local_value_callback + )) { + return false; + } + + if (!zend_opcache_static_cache_clone_request_local_object_members(context, old_object, new_object)) { + return false; + } + + *new_object_ptr = new_object; + + return true; +} + +static bool zend_opcache_static_cache_clone_request_local_object( + zend_opcache_static_cache_request_local_clone_context *context, + zend_object *old_object, + zend_object **new_object_ptr) +{ + zend_object *new_object; + zend_ulong key; + + if (old_object == NULL || zend_object_is_lazy(old_object)) { + return false; + } + + key = (zend_ulong) (uintptr_t) old_object; + new_object = zend_hash_index_find_ptr(&context->objects, key); + + if (new_object != NULL) { + *new_object_ptr = new_object; + + return true; + } + + if (old_object->handlers == zend_get_std_object_handlers()) { + return zend_opcache_static_cache_clone_request_local_std_object(context, old_object, new_object_ptr); + } + + return zend_opcache_static_cache_clone_request_local_safe_direct_object(context, old_object, new_object_ptr); +} + +static bool zend_opcache_static_cache_clone_request_local_value( + zend_opcache_static_cache_request_local_clone_context *context, + zval *dst, + zval *src) +{ + zend_object *object; + + if (Z_ISREF_P(src)) { + return zend_opcache_static_cache_clone_request_local_reference(context, dst, Z_REF_P(src)); + } + + switch (Z_TYPE_P(src)) { + case IS_ARRAY: + return zend_opcache_static_cache_clone_request_local_array(context, dst, src); + case IS_OBJECT: + if (!zend_opcache_static_cache_clone_request_local_object(context, Z_OBJ_P(src), &object)) { + return false; + } + + GC_ADDREF(object); + ZVAL_OBJ(dst, object); + + return true; + default: + ZVAL_COPY(dst, src); + + return true; + } +} + +static bool zend_opcache_static_cache_clone_request_local_slot_value(zval *dst, zval *src) +{ + zend_opcache_static_cache_request_local_clone_context context; + bool result; + + if (Z_TYPE_P(src) == IS_ARRAY) { + if (!zend_opcache_static_cache_value_needs_request_local_clone(src)) { + ZVAL_COPY(dst, src); + + return true; + } + + zend_opcache_static_cache_request_local_clone_context_init(&context); + result = zend_opcache_static_cache_clone_request_local_array_ex(&context, dst, src, true); + zend_opcache_static_cache_request_local_clone_context_destroy(&context); + + return result; + } + + if (!zend_opcache_static_cache_value_needs_request_local_clone(src)) { + ZVAL_COPY(dst, src); + + return true; + } + + zend_opcache_static_cache_request_local_clone_context_init(&context); + result = zend_opcache_static_cache_clone_request_local_value(&context, dst, src); + zend_opcache_static_cache_request_local_clone_context_destroy(&context); + + return result; +} + +static HashTable *zend_opcache_static_cache_request_local_slots(void) +{ + HashTable **slots_ptr = zend_opcache_static_cache_active_request_local_slots_ptr(); + + if (*slots_ptr == NULL) { + ALLOC_HASHTABLE(*slots_ptr); + zend_hash_init(*slots_ptr, 0, NULL, zend_opcache_static_cache_request_local_slot_dtor, 0); + } + + return *slots_ptr; +} + +static zend_never_inline void zend_opcache_static_cache_reacquire_read_lock_or_fail(const char *cache_name) +{ + if (!zend_opcache_static_cache_rlock()) { + zend_error_noreturn(E_ERROR, "Unable to reacquire the %s read lock after userland execution", cache_name); + } +} + +static zend_never_inline bool zend_opcache_static_cache_reacquire_write_lock_or_fail(const char *cache_name) +{ + if (!zend_opcache_static_cache_wlock()) { + zend_error_noreturn(E_ERROR, "Unable to reacquire the %s write lock after userland execution", cache_name); + } + + if (!zend_opcache_static_cache_header_init_locked()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to initialize the %s header", cache_name); + + return false; + } + + return true; +} + +static bool zend_opcache_static_cache_materialize_payload_locked( + zend_string *key, + uint8_t value_type, + uint32_t value_offset, + uint32_t value_len, + zval *return_value, + bool throw_if_missing, + const char *cache_name) +{ + const unsigned char *position, *end; + php_unserialize_data_t var_hash; + uint32_t copied_value_len; + bool ref_registered; + unsigned char *payload_copy; + int result; + + switch (value_type) { + case ZEND_OPCACHE_STATIC_CACHE_VALUE_SERIALIZED: + copied_value_len = value_len; + payload_copy = emalloc(copied_value_len); + memcpy(payload_copy, zend_opcache_static_cache_ptr(value_offset), copied_value_len); + zend_opcache_static_cache_unlock(); + + ZVAL_UNDEF(return_value); + position = payload_copy; + end = position + copied_value_len; + PHP_VAR_UNSERIALIZE_INIT(var_hash); + result = php_var_unserialize(return_value, &position, end, &var_hash); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + efree(payload_copy); + + zend_opcache_static_cache_reacquire_read_lock_or_fail(cache_name); + + if (result != 1 || position != end) { + if (Z_TYPE_P(return_value) != IS_UNDEF) { + zval_ptr_dtor(return_value); + ZVAL_UNDEF(return_value); + } + + if (!EG(exception) && throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" is corrupted", cache_name, ZSTR_VAL(key)); + } + + return false; + } + + return true; + case ZEND_OPCACHE_STATIC_CACHE_VALUE_OPCACHE_SERIALIZED: + copied_value_len = value_len; + payload_copy = emalloc(copied_value_len); + memcpy(payload_copy, zend_opcache_static_cache_ptr(value_offset), copied_value_len); + zend_opcache_static_cache_unlock(); + + ZVAL_UNDEF(return_value); + result = (zend_opcache_static_cache_capture_active + ? zend_opcache_unserialize_ex( + return_value, + payload_copy, + copied_value_len, + zend_opcache_static_cache_capture_decoded_value, + zend_opcache_static_cache_capture_decoded_reachable_value + ) + : zend_opcache_unserialize( + return_value, + payload_copy, + copied_value_len + ) + ); + efree(payload_copy); + + zend_opcache_static_cache_reacquire_read_lock_or_fail(cache_name); + + if (!result) { + if (Z_TYPE_P(return_value) != IS_UNDEF) { + zval_ptr_dtor(return_value); + ZVAL_UNDEF(return_value); + } + + if (!EG(exception) && throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" is corrupted", cache_name, ZSTR_VAL(key)); + } + + return false; + } + + return true; + case ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH: + ref_registered = zend_opcache_static_cache_has_request_shared_graph_ref(value_offset); + if (!ref_registered && !zend_opcache_static_cache_shared_graph_acquire_locked(value_offset)) { + if (throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" is corrupted", cache_name, ZSTR_VAL(key)); + } + + return false; + } + + zend_opcache_static_cache_unlock(); + ZVAL_UNDEF(return_value); + result = zend_opcache_static_cache_fetch_shared_graph( + (const unsigned char *) zend_opcache_static_cache_ptr(value_offset), + value_len, + return_value + ); + zend_opcache_static_cache_reacquire_read_lock_or_fail(cache_name); + + if (!result) { + if (Z_TYPE_P(return_value) != IS_UNDEF) { + zval_ptr_dtor(return_value); + ZVAL_UNDEF(return_value); + } + + if (!ref_registered && zend_opcache_static_cache_shared_graph_release_ref_locked(value_offset)) { + zend_opcache_static_cache_defer_retired_shared_graph_free(value_offset); + } + + if (!EG(exception) && throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" is corrupted", cache_name, ZSTR_VAL(key)); + } + + return false; + } + + if (!ref_registered) { + zend_opcache_static_cache_register_shared_graph_ref(value_offset); + } + + return true; + default: + return false; + } +} + +static bool zend_opcache_static_cache_fetch_request_local_slot(zend_string *key, uint64_t mutation_epoch, zval *return_value) +{ + zend_opcache_static_cache_request_local_slot *slot; + HashTable **slots_ptr = zend_opcache_static_cache_active_request_local_slots_ptr(); + + if (*slots_ptr == NULL) { + return false; + } + + slot = zend_hash_find_ptr(*slots_ptr, key); + if (slot == NULL) { + return false; + } + + if (slot->mutation_epoch != mutation_epoch) { + zend_hash_del(*slots_ptr, key); + + return false; + } + + if (!zend_opcache_static_cache_clone_request_local_slot_value(return_value, &slot->value)) { + zend_hash_del(*slots_ptr, key); + + return false; + } + + return true; +} + +static void zend_opcache_static_cache_store_request_local_value_slot(zend_string *key, uint64_t mutation_epoch, zval *value) +{ + zend_opcache_static_cache_request_local_slot *slot; + zval slot_zv; + + slot = emalloc(sizeof(zend_opcache_static_cache_request_local_slot)); + slot->mutation_epoch = mutation_epoch; + ZVAL_UNDEF(&slot->value); + if (!zend_opcache_static_cache_clone_request_local_slot_value(&slot->value, value)) { + ZVAL_PTR(&slot_zv, slot); + zend_opcache_static_cache_request_local_slot_dtor(&slot_zv); + + return; + } + zend_hash_update_ptr(zend_opcache_static_cache_request_local_slots(), key, slot); +} + +static bool zend_opcache_static_cache_fetch_finish( + zend_string *key, + uint64_t mutation_epoch, + zval *return_value, + bool use_request_local_slot) +{ + if (use_request_local_slot) { + /* Keep a request-local prototype. Later fetches clone object handles out + * of this prototype instead of rematerializing from SHM. */ + zend_opcache_static_cache_store_request_local_value_slot(key, mutation_epoch, return_value); + } + + return true; +} + +static void zend_opcache_static_cache_release_request_local_slot_context(HashTable **slots_ptr) +{ + if (*slots_ptr == NULL) { + return; + } + + zend_hash_destroy(*slots_ptr); + FREE_HASHTABLE(*slots_ptr); + *slots_ptr = NULL; +} + +static bool zend_opcache_static_cache_is_expired(const zend_opcache_static_cache_entry *entry, uint64_t now) +{ + return entry->state == ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED && entry->expires_at != 0 && entry->expires_at <= now; +} + +static bool zend_opcache_static_cache_maybe_expired(const zend_opcache_static_cache_entry *entry, uint64_t *now) +{ + if (entry->state != ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED || entry->expires_at == 0) { + return false; + } + + if (*now == 0) { + *now = (uint64_t) time(NULL); + } + + return zend_opcache_static_cache_is_expired(entry, *now); +} + +static bool zend_opcache_static_cache_find_slot_in_header_locked( + zend_opcache_static_cache_header *header, + zend_string *key, + zend_ulong hash, + uint32_t *slot_index, + bool *found, + bool delete_expired) +{ + zend_opcache_static_cache_entry *entries, *entry; + uint64_t now = 0; + uint32_t index, first_tombstone = UINT32_MAX, step; + + if (header == NULL) { + return false; + } + + entries = zend_opcache_static_cache_entries(header); + index = (uint32_t) (hash % header->capacity); + + for (step = 0; step < header->capacity; step++) { + entry = &entries[index]; + + if (entry->state == ZEND_OPCACHE_STATIC_CACHE_ENTRY_EMPTY) { + *slot_index = first_tombstone != UINT32_MAX ? first_tombstone : index; + *found = false; + + return true; + } + + if (entry->state == ZEND_OPCACHE_STATIC_CACHE_ENTRY_TOMBSTONE) { + if (first_tombstone == UINT32_MAX) { + first_tombstone = index; + } + } else if (zend_opcache_static_cache_maybe_expired(entry, &now)) { + if (delete_expired) { + zend_opcache_static_cache_delete_entry_locked(entry, header); + } + + if (first_tombstone == UINT32_MAX) { + first_tombstone = index; + } + } else if (zend_opcache_static_cache_key_equals(entry, key, hash)) { + *slot_index = index; + *found = true; + + return true; + } + + ++index; + + if (index == header->capacity) { + index = 0; + } + } + + if (first_tombstone != UINT32_MAX) { + *slot_index = first_tombstone; + *found = false; + + return true; + } + + return false; +} + +static bool zend_opcache_static_cache_find_slot_locked( + zend_string *key, + zend_ulong hash, + zend_opcache_static_cache_header **header_ptr, + uint32_t *slot_index, + bool *found, + bool delete_expired) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + + if (!header || !zend_opcache_static_cache_header_init_locked()) { + return false; + } + + if (header_ptr != NULL) { + *header_ptr = header; + } + + return zend_opcache_static_cache_find_slot_in_header_locked(header, key, hash, slot_index, found, delete_expired); +} + +static bool zend_opcache_static_cache_find_slot_for_read_locked( + zend_string *key, + zend_ulong hash, + zend_opcache_static_cache_header **header_ptr, + uint32_t *slot_index, + bool *found) +{ + return zend_opcache_static_cache_find_slot_locked(key, hash, header_ptr, slot_index, found, false); +} + +static bool zend_opcache_static_cache_find_slot_for_write_locked( + zend_string *key, + zend_ulong hash, + zend_opcache_static_cache_header **header_ptr, + uint32_t *slot_index, + bool *found) +{ + return zend_opcache_static_cache_find_slot_locked(key, hash, header_ptr, slot_index, found, true); +} + +static bool zend_opcache_static_cache_expunge_expired_locked(void) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + zend_opcache_static_cache_entry *entries; + uint64_t now; + uint32_t index; + bool removed = false; + + if (!header || !zend_opcache_static_cache_header_init_locked()) { + return false; + } + + now = (uint64_t) time(NULL); + entries = zend_opcache_static_cache_entries(header); + for (index = 0; index < header->capacity; index++) { + if (zend_opcache_static_cache_is_expired(&entries[index], now)) { + zend_opcache_static_cache_delete_entry_locked(&entries[index], header); + removed = true; + } + } + + return removed; +} + +static bool zend_opcache_static_cache_payload_can_fit_locked(size_t size) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + size_t total_size; + + if (!header || size == 0 || size > UINT32_MAX - sizeof(zend_opcache_static_cache_block)) { + return false; + } + + total_size = ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + size); + + return total_size <= UINT32_MAX && total_size <= header->data_size; +} + +static void zend_opcache_static_cache_handle_store_failure(const char *failure_message, bool throw_on_failure) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + + if (context->strict_store_failure && !throw_on_failure) { + zend_opcache_static_cache_mark_publish_skipped(context); + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", failure_message); + + return; + } + + if (throw_on_failure) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", failure_message); + } +} + +static void zend_opcache_static_cache_handle_store_failure_locked(const char *failure_message, bool throw_on_failure) +{ + zend_opcache_static_cache_handle_store_failure(failure_message, throw_on_failure); +} + +static bool zend_opcache_static_cache_find_unstorable_value( + zval *value, + HashTable *seen_arrays, + HashTable *seen_objects, + const char **failure_message) +{ + zend_object *object; + zend_ulong key; + zval *element, *property, *end; + + ZVAL_DEREF(value); + if (Z_TYPE_P(value) == IS_RESOURCE) { + *failure_message = "resources cannot be stored in the static cache"; + + return true; + } + + if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ce_closure) { + *failure_message = "Closure objects cannot be stored in the static cache"; + + return true; + } + + if (Z_TYPE_P(value) == IS_ARRAY) { + key = (zend_ulong) (uintptr_t) Z_ARR_P(value); + if (zend_hash_index_exists(seen_arrays, key)) { + return false; + } + zend_hash_index_add_empty_element(seen_arrays, key); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value), element) { + if (zend_opcache_static_cache_find_unstorable_value(element, seen_arrays, seen_objects, failure_message)) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; + } + + if (Z_TYPE_P(value) == IS_OBJECT) { + object = Z_OBJ_P(value); + key = (zend_ulong) (uintptr_t) object; + if (zend_hash_index_exists(seen_objects, key)) { + return false; + } + zend_hash_index_add_empty_element(seen_objects, key); + + if (object->ce->default_properties_count != 0) { + property = object->properties_table; + end = property + object->ce->default_properties_count; + do { + if (Z_TYPE_P(property) != IS_UNDEF && + zend_opcache_static_cache_find_unstorable_value(property, seen_arrays, seen_objects, failure_message) + ) { + return true; + } + property++; + } while (property != end); + } + + if (object->properties != NULL) { + ZEND_HASH_FOREACH_VAL(object->properties, element) { + if (Z_TYPE_P(element) == IS_INDIRECT) { + element = Z_INDIRECT_P(element); + if (Z_TYPE_P(element) == IS_UNDEF) { + continue; + } + } + + if (zend_opcache_static_cache_find_unstorable_value(element, seen_arrays, seen_objects, failure_message)) { + return true; + } + } ZEND_HASH_FOREACH_END(); + } + } + + return false; +} + +static bool zend_opcache_static_cache_validate_storable_value(zval *value, bool throw_on_failure) +{ + const char *failure_message = NULL; + HashTable seen_arrays, seen_objects; + bool found; + + ZVAL_DEREF(value); + if (Z_TYPE_P(value) != IS_ARRAY && Z_TYPE_P(value) != IS_OBJECT) { + return true; + } + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&seen_objects, 8, NULL, NULL, 0); + found = zend_opcache_static_cache_find_unstorable_value(value, &seen_arrays, &seen_objects, &failure_message); + zend_hash_destroy(&seen_objects); + zend_hash_destroy(&seen_arrays); + if (EG(exception)) { + return false; + } + + if (!found) { + return true; + } + + if (failure_message != NULL) { + zend_opcache_static_cache_handle_store_failure(failure_message, throw_on_failure); + } + + return false; +} + +static unsigned char *zend_opcache_static_cache_reserve_combined_offset_value_locked( + uint32_t reusable_offset, + zend_string *key, + size_t payload_size, + uint32_t *value_offset, + uint32_t *key_offset) +{ + uint32_t base_offset; + size_t key_size, total_size; + + key_size = ZSTR_LEN(key) + 1; + if (payload_size > SIZE_MAX - key_size) { + return NULL; + } + + total_size = payload_size + key_size; + if (reusable_offset != 0 && zend_opcache_static_cache_block_payload_capacity(reusable_offset) >= total_size) { + base_offset = reusable_offset; + } else { + base_offset = zend_opcache_static_cache_alloc_locked(total_size, NULL); + if (base_offset == 0) { + return NULL; + } + } + + *value_offset = base_offset; + *key_offset = base_offset + (uint32_t) payload_size; + + return (unsigned char *) zend_opcache_static_cache_ptr(base_offset); +} + +static bool zend_opcache_static_cache_publish_combined_offset_value_locked( + uint32_t reusable_offset, + zend_string *key, + size_t payload_size, + const void *payload_source, + uint32_t *value_offset, + uint32_t *key_offset) +{ + unsigned char *payload; + + if (payload_source == NULL) { + return false; + } + + payload = zend_opcache_static_cache_reserve_combined_offset_value_locked( + reusable_offset, + key, + payload_size, + value_offset, + key_offset + ); + if (payload == NULL) { + return false; + } + + memcpy(payload, payload_source, payload_size); + memcpy(payload + payload_size, ZSTR_VAL(key), ZSTR_LEN(key) + 1); + + return true; +} + +static bool zend_opcache_static_cache_retry_store_after_pressure_locked( + bool *retried_expired, + bool *retried_compact, + bool *retried_clear, + size_t required_payload_size, + bool allow_clear) +{ + if (!*retried_expired) { + *retried_expired = true; + + if (zend_opcache_static_cache_expunge_expired_locked()) { + return true; + } + } + + if (required_payload_size != 0 && !*retried_compact) { + if (zend_opcache_static_cache_compact_to_fit_locked(required_payload_size)) { + *retried_compact = true; + + return true; + } + } + + if (allow_clear && zend_opcache_static_cache_active_context()->clear_on_pressure && !*retried_clear) { + *retried_clear = true; + + return zend_opcache_static_cache_has_all_entry_locks() && zend_opcache_static_cache_clear_locked(); + } + + return false; +} + +static void zend_opcache_static_cache_init_prepared_value(zend_opcache_static_cache_prepared_value *prepared) +{ + memset(prepared, 0, sizeof(*prepared)); + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL; +} + +bool zend_opcache_static_cache_clear_locked(void) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + zend_opcache_static_cache_entry *entries; + uint64_t mutation_epoch; + uint32_t index; + + if (!header || !zend_opcache_static_cache_header_init_locked()) { + return false; + } + + mutation_epoch = header->mutation_epoch; + entries = zend_opcache_static_cache_entries(header); + for (index = 0; index < header->capacity; index++) { + if (entries[index].state == ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED) { + zend_opcache_static_cache_release_entry_storage_locked(&entries[index]); + } + } + + memset(entries, 0, sizeof(zend_opcache_static_cache_entry) * header->capacity); + + header->count = 0; + header->mutation_epoch = mutation_epoch + 1; + if (header->mutation_epoch == 0) { + header->mutation_epoch = 1; + } + + zend_opcache_static_cache_mark_publish_skipped(zend_opcache_static_cache_active_context()); + + return true; +} + +bool zend_opcache_static_cache_prepare_value( + zend_opcache_static_cache_prepared_value *prepared, + zend_string *key, + zval *value, + bool throw_on_failure, + bool lock_held) +{ + php_serialize_data_t var_hash; + smart_str serialized = {0}; + size_t shared_graph_len, encoded_len; + unsigned char *encoded; + bool failed_unstorable; + + if (prepared == NULL) { + return false; + } + + zend_opcache_static_cache_init_prepared_value(prepared); + ZVAL_DEREF(value); + prepared->hash = zend_string_hash_val(key); + + if (Z_TYPE_P(value) == IS_RESOURCE) { + zend_opcache_static_cache_handle_store_failure( + "resources cannot be stored in the static cache", + throw_on_failure + ); + + return false; + } + + if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ce_closure) { + zend_opcache_static_cache_handle_store_failure( + "Closure objects cannot be stored in the static cache", + throw_on_failure + ); + + return false; + } + + if (!zend_opcache_static_cache_validate_storable_value(value, throw_on_failure)) { + return false; + } + + switch (Z_TYPE_P(value)) { + case IS_NULL: + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL; + return true; + case IS_TRUE: + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_TRUE; + return true; + case IS_FALSE: + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_FALSE; + return true; + case IS_LONG: + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_LONG; + prepared->long_value = Z_LVAL_P(value); + return true; + case IS_DOUBLE: + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_DOUBLE; + prepared->double_value = Z_DVAL_P(value); + return true; + case IS_STRING: + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_STRING; + prepared->value_len = (uint32_t) Z_STRLEN_P(value); + prepared->payload_size = Z_STRLEN_P(value) + 1; + prepared->payload_used_size = prepared->payload_size; + prepared->payload_source = (const unsigned char *) Z_STRVAL_P(value); + return true; + default: + /* The distinct-key write benchmark stores the same source graph many times + * in one request. Reusing a request-local prepared shared graph is only + * safe when we can prove the source graph stayed clean, so the memo layer + * tracks reachable array/object mutations and excludes internal safe-direct + * objects whose state may change outside those hooks. */ + if (zend_opcache_static_cache_prepare_memo_fetch(value, prepared)) { + return true; + } + + shared_graph_len = 0; + if (zend_opcache_static_cache_calculate_shared_graph_size(value, &shared_graph_len)) { + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH; + prepared->value_len = (uint32_t) shared_graph_len; + prepared->payload_size = shared_graph_len; + + /* Explicit stores keep shared-graph construction out of the write lock + * regardless of payload size. Even small direct builds lengthen the + * critical section enough to regress contended writers. */ + if (lock_held) { + return true; + } + + prepared->owned_buffer = emalloc(shared_graph_len); + if (zend_opcache_static_cache_build_shared_graph_in_place( + value, + prepared->owned_buffer, + shared_graph_len, + &prepared->payload_used_size + ) + ) { + prepared->payload_source = prepared->owned_buffer; + zend_opcache_static_cache_prepare_memo_store(value, prepared); + + return true; + } + + if (EG(exception)) { + return false; + } + + efree(prepared->owned_buffer); + prepared->owned_buffer = NULL; + } + + if (EG(exception)) { + return false; + } + + encoded = NULL; + encoded_len = 0; + failed_unstorable = false; + if (zend_opcache_serialize_ex(&encoded, &encoded_len, value, &failed_unstorable)) { + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_OPCACHE_SERIALIZED; + prepared->value_len = (uint32_t) encoded_len; + prepared->payload_size = encoded_len; + prepared->payload_used_size = encoded_len; + prepared->owned_buffer = encoded; + prepared->payload_source = encoded; + + return true; + } + + if (failed_unstorable) { + zend_opcache_static_cache_handle_store_failure( + "resources and Closure objects cannot be stored in the static cache", + throw_on_failure + ); + + return false; + } + + if (EG(exception)) { + return false; + } + + PHP_VAR_SERIALIZE_INIT(var_hash); + php_var_serialize(&serialized, value, &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + if (serialized.s == NULL) { + if (EG(exception)) { + return false; + } + + zend_opcache_static_cache_handle_store_failure( + "failed to serialize value for cache storage", + throw_on_failure + ); + + return false; + } + + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_SERIALIZED; + prepared->value_len = (uint32_t) ZSTR_LEN(serialized.s); + prepared->payload_size = ZSTR_LEN(serialized.s); + prepared->payload_used_size = prepared->payload_size; + prepared->owned_string = serialized.s; + prepared->payload_source = (const unsigned char *) ZSTR_VAL(serialized.s); + serialized.s = NULL; + + return true; + } +} + +void zend_opcache_static_cache_destroy_prepared_value(zend_opcache_static_cache_prepared_value *prepared) +{ + if (prepared == NULL) { + return; + } + + if (prepared->owned_buffer != NULL) { + efree(prepared->owned_buffer); + } + + if (prepared->owned_string != NULL) { + zend_string_release(prepared->owned_string); + } + + zend_opcache_static_cache_init_prepared_value(prepared); +} + +bool zend_opcache_static_cache_store_prepared_locked( + zend_string *key, + zval *value, + const zend_opcache_static_cache_prepared_value *prepared, + zend_long ttl, + bool throw_on_failure) +{ + const char *failure_message; + zend_opcache_static_cache_header *header; + zend_opcache_static_cache_entry *entries, *entry; + zend_long new_long_value = 0; + php_serialize_data_t var_hash; + smart_str serialized = {0}; + uint64_t expires_at; + uint32_t slot_index, offset = 0, graph_offset = 0, reusable_offset, old_key_offset = 0, old_value_offset = 0, + new_key_offset = 0, new_value_offset = 0, new_value_len = 0, combined_reusable_offset = 0; + uint16_t old_reserved = 0, new_reserved = 0; + uint8_t old_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL, new_value_type; + size_t encoded_len, serialized_len, key_payload_size, failed_payload_size; + bool found, retried_expired = false, retried_compact = false, retried_clear = false, allow_clear, old_combined, use_combined_publish, failed_unstorable; + unsigned char *encoded, *combined_payload; + double new_double_value = 0; + + ZVAL_DEREF(value); + + if (prepared == NULL) { + return false; + } + + key_payload_size = ZSTR_LEN(key) + 1; + +retry_store: + old_key_offset = 0; + old_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL; + old_value_offset = 0; + old_reserved = 0; + combined_reusable_offset = 0; + new_key_offset = 0; + new_value_offset = 0; + new_value_len = 0; + new_reserved = 0; + new_value_type = prepared->value_type; + new_long_value = 0; + new_double_value = 0; + failed_payload_size = 0; + expires_at = ttl == 0 ? 0 : (uint64_t) time(NULL) + (uint64_t) ttl; + + if (!zend_opcache_static_cache_find_slot_for_write_locked(key, prepared->hash, &header, &slot_index, &found)) { + if (zend_opcache_static_cache_retry_store_after_pressure_locked(&retried_expired, &retried_compact, &retried_clear, 0, true)) { + goto retry_store; + } + + zend_opcache_static_cache_handle_store_failure_locked("cache hash table is full", throw_on_failure); + + return false; + } + + entries = zend_opcache_static_cache_entries(header); + entry = &entries[slot_index]; + + if (found) { + old_key_offset = entry->key_offset; + old_value_type = entry->value_type; + old_value_offset = entry->value_offset; + old_reserved = entry->reserved; + } + + old_combined = found && (old_reserved & ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY) != 0; + reusable_offset = found && old_value_type != ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH && !old_combined ? old_value_offset : 0; + new_key_offset = found ? old_key_offset : 0; + + /* Offset-backed payloads prefer the combined publish path so a new entry, and + * a slot that was already stored in combined form, can replace value+key with + * one allocator decision. That keeps overwrite bookkeeping local to this slot + * instead of bouncing between separate key and value lifetimes. */ + use_combined_publish = zend_opcache_static_cache_value_uses_offset(prepared->value_type) && (!found || old_combined); + + /* In-place reuse is intentionally narrower than combined publish itself. + * Generic combined payloads only reuse the hot-key case, but prepared shared + * graphs are already built outside the lock. For those explicit-store writes + * we can afford a cheap overwrite check on every matching key to avoid the + * allocator churn that still shows up in distinct-key contention. */ + if (old_combined && old_value_offset != 0) { + if (old_value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { + if (prepared->payload_source != NULL && + zend_opcache_static_cache_shared_graph_can_overwrite_payload_locked(old_value_offset) && + (prepared->value_type != ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH || + (prepared->payload_source != NULL && zend_opcache_static_cache_shared_graph_copy_fits_buffer( + prepared->payload_source, + prepared->payload_size, + prepared->payload_used_size, + (const unsigned char *) zend_opcache_static_cache_ptr(old_value_offset), + prepared->payload_size + ) + ) + ) + ) { + combined_reusable_offset = old_value_offset; + } + } else if (header->count == 1) { + combined_reusable_offset = old_value_offset; + } + } + + if (!use_combined_publish && (!found || old_combined)) { + new_key_offset = zend_opcache_static_cache_alloc_locked(key_payload_size, ZSTR_VAL(key)); + if (new_key_offset == 0) { + failure_message = "not enough shared memory left"; + failed_payload_size = key_payload_size; + allow_clear = zend_opcache_static_cache_payload_can_fit_locked(key_payload_size); + + goto store_failed; + } + } + + switch (prepared->value_type) { + case ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL: + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL; + break; + case ZEND_OPCACHE_STATIC_CACHE_VALUE_TRUE: + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_TRUE; + break; + case ZEND_OPCACHE_STATIC_CACHE_VALUE_FALSE: + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_FALSE; + break; + case ZEND_OPCACHE_STATIC_CACHE_VALUE_LONG: + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_LONG; + new_long_value = prepared->long_value; + break; + case ZEND_OPCACHE_STATIC_CACHE_VALUE_DOUBLE: + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_DOUBLE; + new_double_value = prepared->double_value; + break; + case ZEND_OPCACHE_STATIC_CACHE_VALUE_STRING: + case ZEND_OPCACHE_STATIC_CACHE_VALUE_OPCACHE_SERIALIZED: + case ZEND_OPCACHE_STATIC_CACHE_VALUE_SERIALIZED: + if (use_combined_publish) { + if (!zend_opcache_static_cache_publish_combined_offset_value_locked( + combined_reusable_offset, + key, + prepared->payload_size, + prepared->payload_source, + &new_value_offset, + &new_key_offset) + ) { + failure_message = "not enough shared memory left"; + failed_payload_size = prepared->payload_size + key_payload_size; + allow_clear = zend_opcache_static_cache_payload_can_fit_locked(prepared->payload_size + key_payload_size); + + goto store_failed; + } + + new_reserved = ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY; + } else { + offset = zend_opcache_static_cache_write_payload_locked( + reusable_offset, + prepared->payload_size, + prepared->payload_source); + if (offset == 0) { + failure_message = "not enough shared memory left"; + failed_payload_size = prepared->payload_size; + allow_clear = zend_opcache_static_cache_payload_can_fit_locked(prepared->payload_size); + + goto store_failed; + } + + new_value_offset = offset; + } + + new_value_type = prepared->value_type; + new_value_len = prepared->value_len; + break; + case ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH: + if (use_combined_publish) { + /* Shared-graph fetch helpers treat value_offset as the start of the SHM + * payload, so the graph must stay at the block base. Reserve the whole + * combined block first, publish the graph into the base payload, then + * append the key bytes after the graph payload. */ + combined_payload = zend_opcache_static_cache_reserve_combined_offset_value_locked( + prepared->payload_source != NULL ? combined_reusable_offset : 0, + key, + prepared->payload_size, + &new_value_offset, + &new_key_offset + ); + graph_offset = new_value_offset; + + if (combined_payload != NULL) { + /* Prepared shared-graph buffers may contain direct array payloads with + * final-buffer pointers, so the SHM destination must be the build site. */ + if (zend_opcache_static_cache_build_shared_graph_in_place( + value, + combined_payload, + prepared->payload_size, + NULL) + ) { + memcpy(combined_payload + prepared->payload_size, ZSTR_VAL(key), key_payload_size); + new_reserved = ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY; + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH; + new_value_offset = graph_offset; + new_value_len = prepared->value_len; + break; + } + + if (graph_offset != combined_reusable_offset) { + zend_opcache_static_cache_free_locked(graph_offset); + } + graph_offset = 0; + new_value_offset = 0; + new_key_offset = found ? old_key_offset : 0; + + if (EG(exception)) { + return false; + } + } else if ( + zend_opcache_static_cache_payload_can_fit_locked(prepared->payload_size + key_payload_size) && + zend_opcache_static_cache_retry_store_after_pressure_locked(&retried_expired, &retried_compact, &retried_clear, prepared->payload_size + key_payload_size, true) + ) { + goto retry_store; + } + } else { + graph_offset = zend_opcache_static_cache_alloc_locked(prepared->payload_size, NULL); + + if (graph_offset != 0) { + /* Prepared shared-graph buffers may contain direct array payloads with + * final-buffer pointers, so the SHM destination must be the build site. */ + if (zend_opcache_static_cache_build_shared_graph_in_place( + value, + (unsigned char *) zend_opcache_static_cache_ptr(graph_offset), + prepared->payload_size, + NULL) + ) { + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH; + new_value_offset = graph_offset; + new_value_len = prepared->value_len; + break; + } + + zend_opcache_static_cache_free_locked(graph_offset); + + if (EG(exception)) { + return false; + } + } else if ( + zend_opcache_static_cache_payload_can_fit_locked(prepared->payload_size) && + zend_opcache_static_cache_retry_store_after_pressure_locked(&retried_expired, &retried_compact, &retried_clear, prepared->payload_size, true) + ) { + goto retry_store; + } + } + + offset = reusable_offset; + encoded = NULL; + encoded_len = 0; + failed_unstorable = false; + if (zend_opcache_serialize_ex(&encoded, &encoded_len, value, &failed_unstorable)) { + if (use_combined_publish) { + if (!zend_opcache_static_cache_publish_combined_offset_value_locked( + combined_reusable_offset, + key, + encoded_len, + encoded, + &new_value_offset, + &new_key_offset) + ) { + efree(encoded); + failure_message = "not enough shared memory left"; + failed_payload_size = encoded_len + key_payload_size; + allow_clear = zend_opcache_static_cache_payload_can_fit_locked(encoded_len + key_payload_size); + + goto store_failed; + } + + new_reserved = ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY; + } else { + offset = zend_opcache_static_cache_write_payload_locked(offset, encoded_len, encoded); + if (offset == 0) { + efree(encoded); + failure_message = "not enough shared memory left"; + failed_payload_size = encoded_len; + allow_clear = zend_opcache_static_cache_payload_can_fit_locked(encoded_len); + + goto store_failed; + } + + new_value_offset = offset; + } + efree(encoded); + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_OPCACHE_SERIALIZED; + new_value_len = (uint32_t) encoded_len; + break; + } + + if (failed_unstorable) { + failure_message = "resources and Closure objects cannot be stored in the static cache"; + allow_clear = false; + + goto store_failed; + } + + if (EG(exception)) { + return false; + } + + PHP_VAR_SERIALIZE_INIT(var_hash); + php_var_serialize(&serialized, value, &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + if (serialized.s == NULL) { + if (EG(exception)) { + return false; + } + + failure_message = "failed to serialize value for cache storage"; + allow_clear = false; + + goto store_failed; + } + + serialized_len = ZSTR_LEN(serialized.s); + if (use_combined_publish) { + if (!zend_opcache_static_cache_publish_combined_offset_value_locked( + combined_reusable_offset, + key, + serialized_len, + ZSTR_VAL(serialized.s), + &new_value_offset, + &new_key_offset) + ) { + smart_str_free(&serialized); + failure_message = "not enough shared memory left"; + failed_payload_size = serialized_len + key_payload_size; + allow_clear = zend_opcache_static_cache_payload_can_fit_locked(serialized_len + key_payload_size); + + goto store_failed; + } + + new_reserved = ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY; + } else { + offset = zend_opcache_static_cache_write_payload_locked(reusable_offset, serialized_len, ZSTR_VAL(serialized.s)); + if (offset == 0) { + smart_str_free(&serialized); + failure_message = "not enough shared memory left"; + failed_payload_size = serialized_len; + allow_clear = zend_opcache_static_cache_payload_can_fit_locked(serialized_len); + + goto store_failed; + } + + new_value_offset = offset; + } + smart_str_free(&serialized); + new_value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_SERIALIZED; + new_value_len = (uint32_t) serialized_len; + break; + default: + ZEND_UNREACHABLE(); + } + + entry->hash = prepared->hash; + entry->key_offset = new_key_offset; + entry->key_len = (uint32_t) ZSTR_LEN(key); + entry->value_offset = new_value_offset; + entry->value_len = new_value_len; + entry->expires_at = expires_at; + entry->state = ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED; + entry->value_type = new_value_type; + entry->reserved = new_reserved; + entry->long_value = new_long_value; + entry->double_value = new_double_value; + + if (found && old_key_offset != 0 && old_key_offset != new_key_offset && !old_combined) { + zend_opcache_static_cache_free_locked(old_key_offset); + } + + if (found && old_value_offset != 0 && old_value_offset != new_value_offset) { + zend_opcache_static_cache_release_value_storage_locked(old_value_type, old_value_offset); + } + + if (!found) { + header->count++; + } + + zend_opcache_static_cache_bump_mutation_epoch_locked(header); + + return true; + +store_failed: + if (new_value_offset != 0 && new_value_offset != old_value_offset) { + zend_opcache_static_cache_free_locked(new_value_offset); + } + + if (new_key_offset != 0 && new_key_offset != old_key_offset && + (new_reserved & ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY) == 0 + ) { + zend_opcache_static_cache_free_locked(new_key_offset); + } + + if (zend_opcache_static_cache_retry_store_after_pressure_locked(&retried_expired, &retried_compact, &retried_clear, failed_payload_size, allow_clear)) { + goto retry_store; + } + + zend_opcache_static_cache_handle_store_failure_locked(failure_message, throw_on_failure); + + return false; +} + +bool zend_opcache_static_cache_store_locked(zend_string *key, zval *value, zend_long ttl, bool throw_on_failure) +{ + const char *cache_name = zend_opcache_static_cache_active_context()->name; + zend_opcache_static_cache_prepared_value prepared; + bool stored; + + /* Value preparation may invoke userland serialization hooks. Keep the caller's + * lock contract by dropping the write lock only for preparation and + * reacquiring it before returning. */ + zend_opcache_static_cache_unlock(); + if (!zend_opcache_static_cache_prepare_value(&prepared, key, value, throw_on_failure, false)) { + zend_opcache_static_cache_reacquire_write_lock_or_fail(cache_name); + + return false; + } + + if (!zend_opcache_static_cache_reacquire_write_lock_or_fail(cache_name)) { + zend_opcache_static_cache_destroy_prepared_value(&prepared); + + return false; + } + + stored = zend_opcache_static_cache_store_prepared_locked(key, value, &prepared, ttl, throw_on_failure); + zend_opcache_static_cache_destroy_prepared_value(&prepared); + + return stored; +} + +bool zend_opcache_static_cache_fetch_locked(zend_string *key, zval *return_value, bool throw_if_missing, bool *found_ptr, bool use_request_local_slot) +{ + const char *cache_name = zend_opcache_static_cache_active_context()->name; + zend_opcache_static_cache_header *header; + zend_opcache_static_cache_entry *entries, *entry; + zend_opcache_static_cache_lookup_entry *lookup_entries, *lookup_entry; + zend_ulong hash; + uint64_t mutation_epoch, now; + uint32_t way, slot_index; + bool found; + + if (found_ptr != NULL) { + *found_ptr = false; + } + + hash = zend_string_hash_val(key); + + header = zend_opcache_static_cache_header_ptr(); + if (!header || !zend_opcache_static_cache_header_is_initialized_locked()) { + if (throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Cache key \"%s\" was not found", ZSTR_VAL(key)); + } + + return false; + } + entries = zend_opcache_static_cache_entries(header); + mutation_epoch = header->mutation_epoch; + lookup_entries = zend_opcache_static_cache_lookup_cache_set(hash); + + /* Request-local lookup cache: repeated fetches of the same key avoid the + * shared hash-table probe. A cached miss is just as valuable for userland + * exists/fetch probes, and the mutation epoch keeps both hit and miss records + * coherent after another writer updates the segment. */ + for (way = 0; way < ZEND_OPCACHE_STATIC_CACHE_LOOKUP_WAYS; way++) { + lookup_entry = &lookup_entries[way]; + if (lookup_entry->state == ZEND_OPCACHE_STATIC_CACHE_LOOKUP_EMPTY || lookup_entry->hash != hash) { + continue; + } + + if (lookup_entry->mutation_epoch != mutation_epoch) { + zend_opcache_static_cache_lookup_cache_reset_entry(lookup_entry); + continue; + } + + if (lookup_entry->state == ZEND_OPCACHE_STATIC_CACHE_LOOKUP_MISS) { + if (throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Cache key \"%s\" was not found", ZSTR_VAL(key)); + } + + return false; + } + + if (lookup_entry->slot_index >= header->capacity) { + zend_opcache_static_cache_lookup_cache_reset_entry(lookup_entry); + continue; + } + + entry = &entries[lookup_entry->slot_index]; + if (!zend_opcache_static_cache_key_equals(entry, key, hash)) { + zend_opcache_static_cache_lookup_cache_reset_entry(lookup_entry); + continue; + } + + now = 0; + if (zend_opcache_static_cache_maybe_expired(entry, &now)) { + zend_opcache_static_cache_lookup_cache_reset_entry(lookup_entry); + zend_opcache_static_cache_lookup_cache_store_miss(lookup_entries, hash, mutation_epoch); + if (throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Cache key \"%s\" was not found", ZSTR_VAL(key)); + } + + return false; + } + + slot_index = lookup_entry->slot_index; + found = true; + + goto value_found; + } + + if (!zend_opcache_static_cache_find_slot_in_header_locked(header, key, hash, &slot_index, &found, false) || !found) { + zend_opcache_static_cache_lookup_cache_store_miss(lookup_entries, hash, mutation_epoch); + if (throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Cache key \"%s\" was not found", ZSTR_VAL(key)); + } + + return false; + } + + lookup_entry = zend_opcache_static_cache_lookup_cache_select_slot(lookup_entries, hash, mutation_epoch, true); + if (lookup_entry != NULL) { + zend_opcache_static_cache_lookup_cache_store_hit(lookup_entry, hash, mutation_epoch, slot_index); + } + +value_found: + if (found_ptr != NULL) { + *found_ptr = true; + } + + entry = &entries[slot_index]; + if (use_request_local_slot) { + if (zend_opcache_static_cache_fetch_request_local_slot(key, mutation_epoch, return_value)) { + return true; + } + if (EG(exception)) { + return false; + } + } + + switch (entry->value_type) { + case ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL: + ZVAL_NULL(return_value); + return zend_opcache_static_cache_fetch_finish(key, mutation_epoch, return_value, use_request_local_slot); + case ZEND_OPCACHE_STATIC_CACHE_VALUE_TRUE: + ZVAL_TRUE(return_value); + return zend_opcache_static_cache_fetch_finish(key, mutation_epoch, return_value, use_request_local_slot); + case ZEND_OPCACHE_STATIC_CACHE_VALUE_FALSE: + ZVAL_FALSE(return_value); + return zend_opcache_static_cache_fetch_finish(key, mutation_epoch, return_value, use_request_local_slot); + case ZEND_OPCACHE_STATIC_CACHE_VALUE_LONG: + ZVAL_LONG(return_value, entry->long_value); + return zend_opcache_static_cache_fetch_finish(key, mutation_epoch, return_value, use_request_local_slot); + case ZEND_OPCACHE_STATIC_CACHE_VALUE_DOUBLE: + ZVAL_DOUBLE(return_value, entry->double_value); + return zend_opcache_static_cache_fetch_finish(key, mutation_epoch, return_value, use_request_local_slot); + case ZEND_OPCACHE_STATIC_CACHE_VALUE_STRING: + ZVAL_STRINGL(return_value, zend_opcache_static_cache_ptr(entry->value_offset), entry->value_len); + return zend_opcache_static_cache_fetch_finish(key, mutation_epoch, return_value, use_request_local_slot); + case ZEND_OPCACHE_STATIC_CACHE_VALUE_SERIALIZED: + case ZEND_OPCACHE_STATIC_CACHE_VALUE_OPCACHE_SERIALIZED: + case ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH: + if (!zend_opcache_static_cache_materialize_payload_locked( + key, + entry->value_type, + entry->value_offset, + entry->value_len, + return_value, + throw_if_missing, + cache_name) + ) { + return false; + } + + return zend_opcache_static_cache_fetch_finish(key, mutation_epoch, return_value, use_request_local_slot); + default: + if (throw_if_missing) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" has an unknown type", cache_name, ZSTR_VAL(key)); + } + + return false; + } +} + +bool zend_opcache_static_cache_exists_locked(zend_string *key) +{ + zend_ulong hash = zend_string_hash_val(key); + uint32_t slot_index; + bool found; + + if (!zend_opcache_static_cache_find_slot_for_read_locked(key, hash, NULL, &slot_index, &found)) { + return false; + } + + return found; +} + +bool zend_opcache_static_cache_delete_locked(zend_string *key) +{ + zend_opcache_static_cache_header *header; + zend_opcache_static_cache_entry *entries; + zend_ulong hash = zend_string_hash_val(key); + uint32_t slot_index; + bool found; + + if (!zend_opcache_static_cache_find_slot_for_write_locked(key, hash, &header, &slot_index, &found) || !found) { + return true; + } + + entries = zend_opcache_static_cache_entries(header); + zend_opcache_static_cache_delete_entry_locked(&entries[slot_index], header); + + return true; +} + +bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long step, bool decrement, bool insert_if_missing, zend_long *new_value, const char *type_error_message) +{ + zend_opcache_static_cache_header *header; + zend_opcache_static_cache_entry *entries, *entry; + zend_ulong hash = zend_string_hash_val(key); + zval initial_value = {0}; + uint32_t slot_index; + bool found; + + if (!zend_opcache_static_cache_find_slot_for_write_locked(key, hash, &header, &slot_index, &found) || !found) { + if (insert_if_missing) { + ZVAL_LONG(&initial_value, step); + if (zend_opcache_static_cache_store_locked(key, &initial_value, 0, true)) { + *new_value = step; + + return true; + } + + return false; + } + + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Cache key \"%s\" was not found", ZSTR_VAL(key)); + + return false; + } + + entries = zend_opcache_static_cache_entries(header); + entry = &entries[slot_index]; + if (entry->value_type != ZEND_OPCACHE_STATIC_CACHE_VALUE_LONG) { + if (entry->value_type > ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" has an unknown type", zend_opcache_static_cache_active_context()->name, ZSTR_VAL(key)); + } else { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", type_error_message); + } + + return false; + } + + if (decrement) { + entry->long_value -= step; + } else { + entry->long_value += step; + } + + zend_opcache_static_cache_bump_mutation_epoch_locked(header); + *new_value = entry->long_value; + + return true; +} + +void zend_opcache_static_cache_release_request_local_slots(void) +{ + zend_opcache_static_cache_release_request_local_slot_context(&zend_opcache_static_cache_volatile_request_local_slots); + zend_opcache_static_cache_release_request_local_slot_context(&zend_opcache_static_cache_persistent_request_local_slots); +} diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h new file mode 100644 index 000000000000..236f88b9d0a8 --- /dev/null +++ b/ext/opcache/zend_static_cache_internal.h @@ -0,0 +1,1083 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_STATIC_CACHE_INTERNAL_H +#define ZEND_STATIC_CACHE_INTERNAL_H + +#include "php.h" + +#include +#ifdef ZTS +# include "TSRM/TSRM.h" +#endif + +#include "Zend/zend_attributes.h" +#include "Zend/zend_ast.h" +#include "Zend/zend_atomic.h" +#include "Zend/zend_enum.h" +#include "Zend/zend_exceptions.h" + +#include "ZendAccelerator.h" +#include "zend_accelerator_module.h" +#include "zend_shared_alloc.h" +#include "zend_smart_str.h" +#include "zend_static_cache.h" + +#include "ext/date/php_date.h" +#include "ext/spl/spl_array.h" +#include "ext/spl/spl_fixedarray.h" +#include "ext/standard/php_var.h" + +#include "SAPI.h" + +#define ZEND_OPCACHE_STATIC_CACHE_MAGIC 0xCAC17E01U +#define ZEND_OPCACHE_STATIC_CACHE_VERSION 1U +#define ZEND_OPCACHE_STATIC_CACHE_MIN_CAPACITY 127U +#define ZEND_OPCACHE_STATIC_CACHE_MAX_CAPACITY 65521U +#define ZEND_OPCACHE_STATIC_CACHE_SLOT_BYTES 256U + +#define ZEND_OPCACHE_STATIC_CACHE_ENTRY_EMPTY 0 +#define ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED 1 +#define ZEND_OPCACHE_STATIC_CACHE_ENTRY_TOMBSTONE 2 + +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL 0 +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_TRUE 1 +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_FALSE 2 +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_LONG 3 +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_DOUBLE 4 +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_STRING 5 +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_SERIALIZED 6 +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_OPCACHE_SERIALIZED 7 +#define ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH 8 + +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_MAGIC 0xCAC17E02U +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VERSION 1U +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_RETIRED (1 << 30) +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_REFCOUNT_MASK (ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_RETIRED - 1) + +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_UNDEF 0 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_NULL 1 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_TRUE 2 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_FALSE 3 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_LONG 4 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DOUBLE 5 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_STRING 6 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_ARRAY 7 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT 8 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_SERIALIZED_OBJECT 9 +#define ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DYNAMIC_ARRAY 10 + +#define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS 256U +#define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_WAYS 2U +#define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_SETS (ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS / ZEND_OPCACHE_STATIC_CACHE_LOOKUP_WAYS) +#define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_INVALID_SLOT UINT32_MAX + +#define ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES 256U + +#define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_EMPTY 0 +#define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_HIT 1 +#define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_MISS 2 + +#define ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY 0x0001U + +#define ZEND_OPCACHE_STATIC_CACHE_PERSISTENT_ATTRIBUTE "opcache\\persistentstatic" +#define ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_ATTRIBUTE "opcache\\volatilestatic" + +#define ZEND_OPCACHE_STATIC_CACHE_STRATEGY_IMMEDIATE 0 +#define ZEND_OPCACHE_STATIC_CACHE_STRATEGY_TRACKING 1 + +#define ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE 1U + +#ifndef ZEND_WIN32 +# define ZEND_OPCACHE_STATIC_CACHE_VOLATILE_SEM_FILENAME_PREFIX ".ZendVolatileCacheSem." +# define ZEND_OPCACHE_STATIC_CACHE_PERSISTENT_SEM_FILENAME_PREFIX ".ZendPersistentCacheSem." +#endif + +typedef enum _zend_opcache_static_cache_kind { + ZEND_OPCACHE_STATIC_CACHE_NONE, + ZEND_OPCACHE_STATIC_CACHE_PERSISTENT, + ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC, + ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING, + ZEND_OPCACHE_STATIC_CACHE_CONFLICT +} zend_opcache_static_cache_kind; + +typedef struct _zend_opcache_static_cache_volatile_static_attribute_config { + zend_long ttl; + zend_long strategy; +} zend_opcache_static_cache_volatile_static_attribute_config; + +typedef struct _zend_opcache_static_cache_runtime { + bool enabled; + bool available; + bool startup_failed; + bool backend_initialized; + size_t configured_memory; + const char *failure_reason; +} zend_opcache_static_cache_runtime; + +typedef struct _zend_opcache_static_cache_storage { + const zend_shared_memory_handlers *handler; + zend_shared_segment **segments; + int segment_count; + size_t size; + const char *model; + bool initialized; + bool initialized_before_request; + bool lock_initialized; +#ifndef ZEND_WIN32 + int lock_file; + char lockfile_name[MAXPATHLEN]; +#ifdef ZTS + zend_thread_rwlock_t zts_lock; + MUTEX_T entry_locks[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; + bool entry_locks_initialized; +#endif +#endif +} zend_opcache_static_cache_storage; + +typedef struct _zend_opcache_static_cache_context { + zend_opcache_static_cache_runtime runtime; + zend_opcache_static_cache_storage storage; + const char *name; + const char *lock_name; +#ifndef ZEND_WIN32 + const char *sem_filename_prefix; +#endif + bool clear_on_pressure; + bool strict_store_failure; +} zend_opcache_static_cache_context; + +/* The same storage primitives serve the explicit volatile cache, VolatileStatic, and + * PersistentStatic. Callers switch this TLS context around short critical sections + * so allocator, lookup-cache, and lock helpers always operate on the right SHM + * segment without threading a context argument through every hot helper. */ +typedef struct _zend_opcache_static_cache_header { + uint32_t magic; + uint32_t version; + uint32_t capacity; + uint32_t count; + uint32_t data_offset; + uint32_t data_size; + uint32_t next_free; + uint32_t free_list; + uint32_t last_block_offset; + uint64_t mutation_epoch; +#ifndef ZEND_WIN32 + uint32_t reserved_lock; +#endif +} zend_opcache_static_cache_header; + +typedef struct _zend_opcache_static_cache_block { + uint32_t size; + uint32_t prev_size; + uint32_t next_free; + uint32_t prev_free; + uint32_t flags; +} zend_opcache_static_cache_block; + +typedef struct _zend_opcache_static_cache_entry { + zend_ulong hash; + uint32_t key_offset; + uint32_t key_len; + uint32_t value_offset; + uint32_t value_len; + uint64_t expires_at; + uint8_t state; + uint8_t value_type; + uint16_t reserved; + zend_long long_value; + double double_value; +} zend_opcache_static_cache_entry; + +typedef struct _zend_opcache_static_cache_lookup_entry { + zend_ulong hash; + uint64_t mutation_epoch; + uint32_t slot_index; + uint8_t state; + uint8_t reserved[3]; +} zend_opcache_static_cache_lookup_entry; + +typedef struct _zend_opcache_static_cache_request_local_slot { + uint64_t mutation_epoch; + zval value; +} zend_opcache_static_cache_request_local_slot; + +typedef struct _zend_opcache_static_cache_prepared_value { + zend_ulong hash; + uint8_t value_type; + uint8_t reserved[3]; + uint32_t value_len; + size_t payload_size; + size_t payload_used_size; + const unsigned char *payload_source; + unsigned char *owned_buffer; + zend_string *owned_string; + zend_long long_value; + double double_value; +} zend_opcache_static_cache_prepared_value; + +typedef struct _zend_opcache_static_cache_class_blob_handle { + zend_class_entry *ce; + zend_string *cache_key; + zend_opcache_static_cache_context *context; + zend_opcache_static_cache_kind kind; + zend_long ttl; + zval root_state; + bool initialized; + bool dirty; + bool request_deferred_publish; +} zend_opcache_static_cache_class_blob_handle; + +typedef struct _zend_opcache_static_cache_function_static_entry { + zval *slot; + zend_string *cache_key; + zend_opcache_static_cache_context *context; + zend_opcache_static_cache_kind kind; + zend_long ttl; + zend_opcache_static_cache_class_blob_handle *class_blob_handle; + zend_string *method_key; + zend_string *var_name; +} zend_opcache_static_cache_function_static_entry; + +typedef struct _zend_opcache_static_cache_static_slot_handle { + zval *slot; + zend_string *cache_key; + zend_opcache_static_cache_context *context; + zend_opcache_static_cache_kind kind; + zend_long ttl; + zend_opcache_static_cache_class_blob_handle *class_blob_handle; + zval original_value; + zend_refcounted *original_root; + uint8_t original_root_type; + bool has_original_value; + bool snapshot_root_published; + bool request_deferred_publish; + bool mutation_dirty; +} zend_opcache_static_cache_static_slot_handle; + +typedef struct _zend_opcache_static_cache_tracked_dependency { + zend_opcache_static_cache_static_slot_handle *single_handle; + HashTable *handles; +} zend_opcache_static_cache_tracked_dependency; + +typedef struct _zend_opcache_static_cache_pending_mutation { + zend_opcache_static_cache_static_slot_handle *handle; + uint32_t depth; + bool dirty; + bool root_array; +} zend_opcache_static_cache_pending_mutation; + +typedef struct _zend_opcache_static_cache_shared_graph_header { + uint32_t magic; + uint32_t version; + uint32_t root_offset; + uint32_t root_type; + zend_atomic_int ref_state; +} zend_opcache_static_cache_shared_graph_header; + +typedef struct _zend_opcache_static_cache_shared_graph_ref { + zend_opcache_static_cache_context *context; + uint32_t payload_offset; +} zend_opcache_static_cache_shared_graph_ref; + +typedef struct _zend_opcache_static_cache_entry_lock { + zend_opcache_static_cache_context *context; + zend_ulong owner_pid; + uint32_t stripe; +} zend_opcache_static_cache_entry_lock; + +typedef struct _zend_opcache_static_cache_shared_graph_value { + uint8_t type; + uint8_t reserved[7]; + union { + zend_long long_value; + double double_value; + uint64_t offset; + } payload; +} zend_opcache_static_cache_shared_graph_value; + +typedef struct _zend_opcache_static_cache_shared_graph_property { + uint32_t name_offset; + uint32_t reserved; + zend_opcache_static_cache_shared_graph_value value; +} zend_opcache_static_cache_shared_graph_property; + +typedef struct _zend_opcache_static_cache_shared_graph_array_element { + zend_ulong h; + uint32_t key_offset; + uint32_t reserved; + zend_opcache_static_cache_shared_graph_value value; +} zend_opcache_static_cache_shared_graph_array_element; + +typedef struct _zend_opcache_static_cache_shared_graph_array { + uint32_t count; + uint32_t next_free; + uint32_t elements_offset; + uint32_t reserved; +} zend_opcache_static_cache_shared_graph_array; + +typedef struct _zend_opcache_static_cache_shared_graph_object { + uint32_t class_name_offset; + uint32_t property_count; + uint32_t properties_offset; + uint32_t reserved; +} zend_opcache_static_cache_shared_graph_object; + +typedef struct _zend_opcache_static_cache_shared_graph_serialized_object { + uint32_t data_len; + uint32_t reserved; + unsigned char data[1]; +} zend_opcache_static_cache_shared_graph_serialized_object; + +typedef struct _zend_opcache_static_cache_shared_graph_calc_context { + size_t size; + HashTable seen_arrays; + HashTable seen_objects; +} zend_opcache_static_cache_shared_graph_calc_context; + +typedef struct _zend_opcache_static_cache_shared_graph_copy_context { + unsigned char *buffer; + size_t size; + size_t position; + HashTable seen_arrays; + HashTable seen_objects; +} zend_opcache_static_cache_shared_graph_copy_context; + +extern zend_class_entry *zend_opcache_static_cache_exception_ce; +extern zend_class_entry *zend_opcache_static_cache_strategy_ce; +extern zend_opcache_static_cache_context zend_opcache_static_cache_volatile_context_state; +extern zend_opcache_static_cache_context zend_opcache_static_cache_persistent_context_state; +extern bool zend_opcache_static_cache_subsystem_disabled; +extern const char *zend_opcache_static_cache_subsystem_failure_reason; +extern ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_volatile_runtime_state; +extern ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_persistent_runtime_state; +extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_attribute_classes; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_attribute_classes_initialized; +extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_ignored_classes; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_ignored_classes_initialized; +extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_function_statics; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_function_statics_initialized; +extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_class_blob_handles; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_class_blob_handles_initialized; +extern bool zend_opcache_static_cache_safe_direct_classes_marked; +extern ZEND_EXT_TLS zend_opcache_static_cache_context *zend_opcache_static_cache_active_context_ptr; +#if defined(ZTS) && !defined(ZEND_WIN32) +extern ZEND_EXT_TLS bool zend_opcache_static_cache_zts_lock_is_write; +#endif +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_roots; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_references; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_arrays; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_objects; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_has_tracked_arrays; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_has_tracked_objects; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_volatile_skip_publish; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_persistent_skip_publish; +extern ZEND_EXT_TLS zend_opcache_static_cache_shared_graph_ref *zend_opcache_static_cache_shared_graph_refs; +extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_shared_graph_ref_count; +extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_shared_graph_ref_capacity; +extern ZEND_EXT_TLS zend_opcache_static_cache_shared_graph_ref *zend_opcache_static_cache_retired_shared_graphs; +extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_retired_shared_graph_count; +extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_retired_shared_graph_capacity; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_last_array_ht; +extern ZEND_EXT_TLS zend_opcache_static_cache_tracked_dependency *zend_opcache_static_cache_last_array_dependency; +extern ZEND_EXT_TLS zend_object *zend_opcache_static_cache_last_object_obj; +extern ZEND_EXT_TLS zend_opcache_static_cache_tracked_dependency *zend_opcache_static_cache_last_object_dependency; +extern ZEND_EXT_TLS zend_opcache_static_cache_pending_mutation zend_opcache_static_cache_pending_mutation_state; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_capture_active; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_capture_available; +extern ZEND_EXT_TLS zend_opcache_static_cache_static_slot_handle *zend_opcache_static_cache_capture_handle; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_capture_arrays; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_capture_objects; +extern ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_volatile_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; +extern ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_persistent_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_volatile_request_local_slots; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_request_local_slots; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_volatile_entry_locks; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_entry_locks; +extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_volatile_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; +extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_persistent_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; + +void zend_opcache_static_cache_reset_runtime(void); +void zend_opcache_static_cache_reset_storage(void); +bool zend_opcache_static_cache_header_init_locked(void); +void zend_opcache_static_cache_free_locked(uint32_t payload_offset); +uint32_t zend_opcache_static_cache_alloc_locked(size_t size, const void *source); +bool zend_opcache_static_cache_compact_to_fit_locked(size_t size); +bool zend_opcache_static_cache_startup_storage_before_request(void); +void zend_opcache_static_cache_shutdown_storage(void); +void zend_opcache_static_cache_ensure_ready(void); +void zend_opcache_static_cache_populate_array(zval *return_value); +bool zend_opcache_static_cache_rlock(void); +bool zend_opcache_static_cache_wlock(void); +void zend_opcache_static_cache_unlock(void); +bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key); +bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key); +bool zend_opcache_static_cache_has_entry_lock(zend_string *key); +void zend_opcache_static_cache_release_entry_lock(zend_string *key); +bool zend_opcache_static_cache_has_all_entry_locks(void); +void zend_opcache_static_cache_release_active_entry_locks(void); +void zend_opcache_static_cache_release_request_entry_locks(void); +void zend_opcache_static_cache_safe_direct_handlers_init(void); +void zend_opcache_static_cache_safe_direct_handlers_destroy(void); +void zend_opcache_static_cache_safe_direct_register_class( + zend_class_entry *ce, + const zend_opcache_static_cache_safe_direct_handlers *handlers); +zend_opcache_static_cache_safe_direct_state_copy_func_t zend_opcache_static_cache_safe_direct_copy_func( + zend_class_entry *ce, + zend_class_entry **base_ce_ptr); +zend_opcache_static_cache_safe_direct_state_has_unstorable_func_t zend_opcache_static_cache_safe_direct_state_has_unstorable_func( + zend_class_entry *ce); +zend_opcache_static_cache_safe_direct_state_serialize_func_t zend_opcache_static_cache_safe_direct_state_serialize_func( + zend_class_entry *ce); +zend_opcache_static_cache_safe_direct_state_unserialize_func_t zend_opcache_static_cache_safe_direct_state_unserialize_func( + zend_class_entry *ce); +bool zend_opcache_static_cache_safe_direct_allows_custom_serializers(zend_class_entry *ce); +bool zend_opcache_static_cache_calculate_shared_graph_size(const zval *value, size_t *buffer_len); +bool zend_opcache_static_cache_build_shared_graph_in_place(const zval *value, unsigned char *buffer, size_t buffer_len, size_t *graph_len); +bool zend_opcache_static_cache_shared_graph_copy_fits_buffer( + const unsigned char *source_buffer, + size_t source_buffer_len, + size_t source_graph_len, + const unsigned char *target_buffer, + size_t target_buffer_len); +bool zend_opcache_static_cache_fetch_shared_graph(const unsigned char *buffer, size_t buffer_len, zval *destination); +bool zend_opcache_static_cache_shared_graph_can_overwrite_payload_locked(uint32_t payload_offset); +bool zend_opcache_static_cache_shared_graph_acquire_locked(uint32_t payload_offset); +bool zend_opcache_static_cache_shared_graph_retire_payload_locked(uint32_t payload_offset); +bool zend_opcache_static_cache_shared_graph_release_ref_locked(uint32_t payload_offset); +bool zend_opcache_static_cache_has_request_shared_graph_ref(uint32_t payload_offset); +void zend_opcache_static_cache_register_shared_graph_ref(uint32_t payload_offset); +void zend_opcache_static_cache_defer_retired_shared_graph_free(uint32_t payload_offset); +void zend_opcache_static_cache_release_request_shared_graph_refs(void); +bool zend_opcache_static_cache_clear_locked(void); +bool zend_opcache_static_cache_prepare_value( + zend_opcache_static_cache_prepared_value *prepared, + zend_string *key, + zval *value, + bool throw_on_failure, + bool lock_held); +void zend_opcache_static_cache_destroy_prepared_value(zend_opcache_static_cache_prepared_value *prepared); +bool zend_opcache_static_cache_store_prepared_locked( + zend_string *key, + zval *value, + const zend_opcache_static_cache_prepared_value *prepared, + zend_long ttl, + bool throw_on_failure); +bool zend_opcache_static_cache_store_locked(zend_string *key, zval *value, zend_long ttl, bool throw_on_failure); +bool zend_opcache_static_cache_fetch_locked(zend_string *key, zval *return_value, bool throw_if_missing, bool *found_ptr, bool use_request_local_slot); +bool zend_opcache_static_cache_exists_locked(zend_string *key); +bool zend_opcache_static_cache_delete_locked(zend_string *key); +bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long step, bool decrement, bool insert_if_missing, zend_long *new_value, const char *type_error_message); +void zend_opcache_static_cache_release_request_local_slots(void); +void zend_opcache_static_cache_update_mutation_hook_state(void); +bool zend_opcache_static_cache_prepare_memo_fetch(zval *value, zend_opcache_static_cache_prepared_value *prepared); +void zend_opcache_static_cache_prepare_memo_store(zval *value, zend_opcache_static_cache_prepared_value *prepared); +void zend_opcache_static_cache_delete_script_keys_locked(zend_persistent_script *persistent_script); +void zend_opcache_static_cache_register_hooks(void); +void zend_opcache_static_cache_unregister_hooks(void); +void zend_opcache_static_cache_request_init(void); +void zend_opcache_static_cache_capture_begin_for_handle(zend_opcache_static_cache_static_slot_handle *handle); +void zend_opcache_static_cache_capture_discard(void); +void zend_opcache_static_cache_capture_decoded_reachable_value(zval *value); +void zend_opcache_static_cache_capture_decoded_value(zval *value); +void zend_opcache_static_cache_request_shutdown(void); + +static zend_always_inline zend_opcache_static_cache_context *zend_opcache_static_cache_active_context(void) +{ + return zend_opcache_static_cache_active_context_ptr != NULL + ? zend_opcache_static_cache_active_context_ptr + : &zend_opcache_static_cache_volatile_context_state + ; +} + +static zend_always_inline void *zend_opcache_static_cache_base(void) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + + if (!storage->initialized || storage->segment_count != 1) { + return NULL; + } + + return storage->segments[0]->p; +} + +static zend_always_inline zend_opcache_static_cache_header *zend_opcache_static_cache_header_ptr(void) +{ + return (zend_opcache_static_cache_header *) zend_opcache_static_cache_base(); +} + +static zend_always_inline char *zend_opcache_static_cache_ptr(uint32_t offset) +{ + return (char *) zend_opcache_static_cache_base() + offset; +} + +static zend_always_inline zend_opcache_static_cache_block *zend_opcache_static_cache_block_ptr(uint32_t offset) +{ + return (zend_opcache_static_cache_block *) zend_opcache_static_cache_ptr(offset); +} + +static zend_always_inline uint32_t zend_opcache_static_cache_block_payload_capacity(uint32_t payload_offset) +{ + zend_opcache_static_cache_block *block; + + if (payload_offset < sizeof(zend_opcache_static_cache_block)) { + return 0; + } + + block = zend_opcache_static_cache_block_ptr(payload_offset - sizeof(zend_opcache_static_cache_block)); + if (block->size < sizeof(zend_opcache_static_cache_block)) { + return 0; + } + + return block->size - (uint32_t) sizeof(zend_opcache_static_cache_block); +} + +static zend_always_inline uint32_t zend_opcache_static_cache_write_payload_locked(uint32_t reusable_offset, size_t size, const void *source) +{ + if (reusable_offset != 0 && zend_opcache_static_cache_block_payload_capacity(reusable_offset) >= size) { + memcpy(zend_opcache_static_cache_ptr(reusable_offset), source, size); + + return reusable_offset; + } + + return zend_opcache_static_cache_alloc_locked(size, source); +} + +static zend_always_inline bool zend_opcache_static_cache_strategy_is_valid(zend_long strategy) +{ + return strategy == ZEND_OPCACHE_STATIC_CACHE_STRATEGY_IMMEDIATE || + strategy == ZEND_OPCACHE_STATIC_CACHE_STRATEGY_TRACKING + ; +} + +static zend_always_inline bool zend_opcache_static_cache_strategy_from_zval(zval *value, zend_long *strategy) +{ + zval *backing_value; + + if (value == NULL || + Z_TYPE_P(value) != IS_OBJECT || + Z_OBJCE_P(value) != zend_opcache_static_cache_strategy_ce + ) { + return false; + } + + backing_value = zend_enum_fetch_case_value(Z_OBJ_P(value)); + if (Z_TYPE_P(backing_value) != IS_LONG) { + return false; + } + + *strategy = Z_LVAL_P(backing_value); + + return zend_opcache_static_cache_strategy_is_valid(*strategy); +} + +static zend_always_inline bool zend_opcache_static_cache_strategy_from_attribute_zval(zval *value, zend_long *strategy) +{ + zend_ast *ast, *class_ast, *case_ast; + zend_string *class_name, *case_name; + + if (zend_opcache_static_cache_strategy_from_zval(value, strategy)) { + return true; + } + + if (value == NULL || Z_TYPE_P(value) != IS_CONSTANT_AST) { + return false; + } + + ast = Z_ASTVAL_P(value); + if (ast->kind != ZEND_AST_CLASS_CONST) { + return false; + } + + class_ast = ast->child[0]; + case_ast = ast->child[1]; + if (class_ast == NULL || + case_ast == NULL || + class_ast->kind != ZEND_AST_ZVAL || + case_ast->kind != ZEND_AST_ZVAL || + Z_TYPE_P(zend_ast_get_zval(class_ast)) != IS_STRING || + Z_TYPE_P(zend_ast_get_zval(case_ast)) != IS_STRING + ) { + return false; + } + + class_name = zend_ast_get_str(class_ast); + if (!zend_string_equals_literal_ci(class_name, "OPcache\\CacheStrategy")) { + return false; + } + + case_name = zend_ast_get_str(case_ast); + if (zend_string_equals_literal(case_name, "Immediate")) { + *strategy = ZEND_OPCACHE_STATIC_CACHE_STRATEGY_IMMEDIATE; + + return true; + } + + if (zend_string_equals_literal(case_name, "Tracking")) { + *strategy = ZEND_OPCACHE_STATIC_CACHE_STRATEGY_TRACKING; + + return true; + } + + return false; +} + +static zend_always_inline void zend_opcache_static_cache_volatile_static_attribute_config_init( + zend_opcache_static_cache_volatile_static_attribute_config *config) +{ + config->ttl = 0; + config->strategy = ZEND_OPCACHE_STATIC_CACHE_STRATEGY_IMMEDIATE; +} + +static zend_always_inline bool zend_opcache_static_cache_volatile_static_attribute_parse_ttl( + zend_attribute *attr, + uint32_t arg_num, + zend_class_entry *scope, + zend_long *ttl, + zend_string **error) +{ + zval ttl_zv; + + ZVAL_COPY_OR_DUP(&ttl_zv, &attr->args[arg_num].value); + if (Z_TYPE(ttl_zv) != IS_LONG) { + zval_ptr_dtor(&ttl_zv); + if (zend_get_attribute_value(&ttl_zv, attr, arg_num, scope) == FAILURE) { + *error = ZSTR_INIT_LITERAL("OPcache\\VolatileStatic ttl must be resolvable", 0); + + return false; + } + } + + if (Z_TYPE(ttl_zv) != IS_LONG) { + *error = zend_strpprintf(0, + "OPcache\\VolatileStatic ttl must be of type int, %s given", + zend_zval_value_name(&ttl_zv) + ); + zval_ptr_dtor(&ttl_zv); + + return false; + } + + if (Z_LVAL(ttl_zv) < 0) { + *error = ZSTR_INIT_LITERAL("OPcache\\VolatileStatic ttl must be greater than or equal to 0", 0); + zval_ptr_dtor(&ttl_zv); + + return false; + } + + *ttl = Z_LVAL(ttl_zv); + zval_ptr_dtor(&ttl_zv); + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_volatile_static_attribute_parse_strategy( + zend_attribute *attr, + uint32_t arg_num, + zend_class_entry *scope, + zend_long *strategy, + zend_string **error) +{ + zval strategy_zv; + + ZVAL_COPY_OR_DUP(&strategy_zv, &attr->args[arg_num].value); + if (!zend_opcache_static_cache_strategy_from_attribute_zval(&strategy_zv, strategy)) { + zval_ptr_dtor(&strategy_zv); + if (zend_get_attribute_value(&strategy_zv, attr, arg_num, scope) == FAILURE) { + *error = ZSTR_INIT_LITERAL("OPcache\\VolatileStatic strategy must be resolvable", 0); + + return false; + } + } + + if (!zend_opcache_static_cache_strategy_from_attribute_zval(&strategy_zv, strategy)) { + *error = zend_strpprintf(0, + "OPcache\\VolatileStatic strategy must be of type OPcache\\CacheStrategy, %s given", + zend_zval_value_name(&strategy_zv) + ); + zval_ptr_dtor(&strategy_zv); + + return false; + } + + zval_ptr_dtor(&strategy_zv); + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_volatile_static_attribute_config_from_attribute( + zend_attribute *attr, + zend_class_entry *scope, + zend_opcache_static_cache_volatile_static_attribute_config *config, + zend_string **error) +{ + uint32_t i; + bool ttl_seen = false, strategy_seen = false; + + zend_opcache_static_cache_volatile_static_attribute_config_init(config); + *error = NULL; + + if (attr == NULL || attr->argc == 0) { + return true; + } + + if (attr->argc > 2) { + *error = ZSTR_INIT_LITERAL("OPcache\\VolatileStatic expects at most 2 arguments", 0); + + return false; + } + + for (i = 0; i < attr->argc; i++) { + if (attr->args[i].name != NULL) { + if (zend_string_equals_literal(attr->args[i].name, "ttl")) { + if (ttl_seen) { + *error = ZSTR_INIT_LITERAL("Named parameter $ttl overwrites previous argument", 0); + + return false; + } + + ttl_seen = true; + if (!zend_opcache_static_cache_volatile_static_attribute_parse_ttl(attr, i, scope, &config->ttl, error)) { + return false; + } + + continue; + } + + if (zend_string_equals_literal(attr->args[i].name, "strategy")) { + if (strategy_seen) { + *error = ZSTR_INIT_LITERAL("Named parameter $strategy overwrites previous argument", 0); + + return false; + } + + strategy_seen = true; + if (!zend_opcache_static_cache_volatile_static_attribute_parse_strategy(attr, i, scope, &config->strategy, error)) { + return false; + } + + continue; + } + + *error = zend_strpprintf(0, "Unknown named parameter $%s", ZSTR_VAL(attr->args[i].name)); + + return false; + } + + if (i == 0) { + if (ttl_seen) { + *error = ZSTR_INIT_LITERAL("Named parameter $ttl overwrites previous argument", 0); + + return false; + } + + ttl_seen = true; + if (!zend_opcache_static_cache_volatile_static_attribute_parse_ttl(attr, i, scope, &config->ttl, error)) { + return false; + } + + continue; + } + + if (strategy_seen) { + *error = ZSTR_INIT_LITERAL("Named parameter $strategy overwrites previous argument", 0); + + return false; + } + + strategy_seen = true; + if (!zend_opcache_static_cache_volatile_static_attribute_parse_strategy(attr, i, scope, &config->strategy, error)) { + return false; + } + } + + return true; +} + +static zend_always_inline zend_opcache_static_cache_context *zend_opcache_static_cache_activate_context(zend_opcache_static_cache_context *context) +{ + zend_opcache_static_cache_context *previous = zend_opcache_static_cache_active_context_ptr; + + zend_opcache_static_cache_active_context_ptr = context; + + return previous; +} + +static zend_always_inline void zend_opcache_static_cache_restore_context(zend_opcache_static_cache_context *context) +{ + zend_opcache_static_cache_active_context_ptr = context; +} + +static zend_always_inline zend_opcache_static_cache_runtime *zend_opcache_static_cache_context_runtime(zend_opcache_static_cache_context *context) +{ + /* Runtime readiness is request-local under ZTS, while storage remains shared. */ + return context == &zend_opcache_static_cache_persistent_context_state + ? &zend_opcache_static_cache_persistent_runtime_state + : &zend_opcache_static_cache_volatile_runtime_state + ; +} + +static zend_always_inline zend_opcache_static_cache_runtime *zend_opcache_static_cache_active_runtime(void) +{ + return zend_opcache_static_cache_context_runtime(zend_opcache_static_cache_active_context()); +} + +static zend_always_inline zend_opcache_static_cache_lookup_entry *zend_opcache_static_cache_active_lookup_entries(void) +{ + return zend_opcache_static_cache_active_context() == &zend_opcache_static_cache_persistent_context_state + ? zend_opcache_static_cache_persistent_lookup_entry_storage + : zend_opcache_static_cache_volatile_lookup_entry_storage + ; +} + +static zend_always_inline HashTable **zend_opcache_static_cache_active_request_local_slots_ptr(void) +{ + return zend_opcache_static_cache_active_context() == &zend_opcache_static_cache_persistent_context_state + ? &zend_opcache_static_cache_persistent_request_local_slots + : &zend_opcache_static_cache_volatile_request_local_slots + ; +} + +static zend_always_inline zend_opcache_static_cache_lookup_entry *zend_opcache_static_cache_lookup_cache_set(zend_ulong hash) +{ + uint32_t set_index = (uint32_t) (hash & (ZEND_OPCACHE_STATIC_CACHE_LOOKUP_SETS - 1)); + + return &zend_opcache_static_cache_active_lookup_entries()[set_index * ZEND_OPCACHE_STATIC_CACHE_LOOKUP_WAYS]; +} + +static zend_always_inline void zend_opcache_static_cache_lookup_cache_store( + zend_opcache_static_cache_lookup_entry *lookup_entry, + zend_ulong hash, + uint64_t mutation_epoch, + uint32_t slot_index, + uint8_t state) +{ + if (lookup_entry == NULL) { + return; + } + + lookup_entry->hash = hash; + lookup_entry->mutation_epoch = mutation_epoch; + lookup_entry->slot_index = slot_index; + lookup_entry->state = state; +} + +static zend_always_inline zend_opcache_static_cache_lookup_entry *zend_opcache_static_cache_lookup_cache_preferred_way( + zend_opcache_static_cache_lookup_entry *lookup_entries, + zend_ulong hash) +{ + uint32_t way = (uint32_t) ((((uint64_t) hash >> 32) ^ hash) & (ZEND_OPCACHE_STATIC_CACHE_LOOKUP_WAYS - 1)); + + return &lookup_entries[way]; +} + +static zend_always_inline zend_opcache_static_cache_lookup_entry *zend_opcache_static_cache_lookup_cache_select_slot( + zend_opcache_static_cache_lookup_entry *lookup_entries, + zend_ulong hash, + uint64_t mutation_epoch, + bool allow_hit_eviction) +{ + zend_opcache_static_cache_lookup_entry *preferred, *alternate; + + if (lookup_entries == NULL) { + return NULL; + } + + preferred = zend_opcache_static_cache_lookup_cache_preferred_way(lookup_entries, hash); + alternate = preferred == &lookup_entries[0] ? &lookup_entries[1] : &lookup_entries[0]; + + /* Keep each set two-way associative: the hash chooses a stable preferred + * way, while the alternate way gives short collision chains a request-local + * fast path without touching the shared entry table. Mutation epochs make + * stale hit/miss entries self-invalidating after any writer updates SHM. */ + if (preferred->state != ZEND_OPCACHE_STATIC_CACHE_LOOKUP_EMPTY && preferred->hash == hash && preferred->mutation_epoch == mutation_epoch) { + return preferred; + } + if (alternate->state != ZEND_OPCACHE_STATIC_CACHE_LOOKUP_EMPTY && alternate->hash == hash && alternate->mutation_epoch == mutation_epoch) { + return alternate; + } + + if (preferred->state == ZEND_OPCACHE_STATIC_CACHE_LOOKUP_EMPTY || preferred->mutation_epoch != mutation_epoch) { + return preferred; + } + if (alternate->state == ZEND_OPCACHE_STATIC_CACHE_LOOKUP_EMPTY || alternate->mutation_epoch != mutation_epoch) { + return alternate; + } + + if (preferred->state == ZEND_OPCACHE_STATIC_CACHE_LOOKUP_MISS) { + return preferred; + } + if (alternate->state == ZEND_OPCACHE_STATIC_CACHE_LOOKUP_MISS) { + return alternate; + } + + return allow_hit_eviction ? preferred : NULL; +} + +static zend_always_inline void zend_opcache_static_cache_lookup_cache_store_miss( + zend_opcache_static_cache_lookup_entry *lookup_entries, + zend_ulong hash, + uint64_t mutation_epoch) +{ + zend_opcache_static_cache_lookup_entry *victim = zend_opcache_static_cache_lookup_cache_select_slot(lookup_entries, hash, mutation_epoch, false); + + if (victim == NULL) { + return; + } + + zend_opcache_static_cache_lookup_cache_store( + victim, + hash, + mutation_epoch, + ZEND_OPCACHE_STATIC_CACHE_LOOKUP_INVALID_SLOT, + ZEND_OPCACHE_STATIC_CACHE_LOOKUP_MISS + ); +} + +static zend_always_inline void zend_opcache_static_cache_lookup_cache_store_hit( + zend_opcache_static_cache_lookup_entry *lookup_entry, + zend_ulong hash, + uint64_t mutation_epoch, + uint32_t slot_index) +{ + zend_opcache_static_cache_lookup_cache_store( + lookup_entry, + hash, + mutation_epoch, + slot_index, + ZEND_OPCACHE_STATIC_CACHE_LOOKUP_HIT + ); +} + +static zend_always_inline bool zend_opcache_static_cache_header_is_initialized_locked(void) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + + return header != NULL && + header->magic == ZEND_OPCACHE_STATIC_CACHE_MAGIC && + header->version == ZEND_OPCACHE_STATIC_CACHE_VERSION + ; +} + +static zend_always_inline void zend_opcache_static_cache_bump_mutation_epoch_locked(zend_opcache_static_cache_header *header) +{ + if (header == NULL) { + return; + } + + /* uint64_t makes wrap-around essentially unreachable in practice, but on + * wrap we skip 0 so a freshly bumped epoch can never coincide with the + * zero-initialized lookup_entry slot value. */ + header->mutation_epoch++; + if (header->mutation_epoch == 0) { + header->mutation_epoch = 1; + } +} + +static zend_always_inline zend_opcache_static_cache_entry *zend_opcache_static_cache_entries(zend_opcache_static_cache_header *header) +{ + return (zend_opcache_static_cache_entry *) ((char *) header + sizeof(zend_opcache_static_cache_header)); +} + +static zend_always_inline bool zend_opcache_static_cache_key_equals( + const zend_opcache_static_cache_entry *entry, + zend_string *key, + zend_ulong hash) +{ + if (entry->state != ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED || entry->hash != hash || entry->key_len != ZSTR_LEN(key)) { + return false; + } + + return memcmp(zend_opcache_static_cache_ptr(entry->key_offset), ZSTR_VAL(key), ZSTR_LEN(key)) == 0; +} + +static zend_always_inline bool zend_opcache_static_cache_value_uses_offset(uint8_t value_type) +{ + return + value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_STRING || + value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_SERIALIZED || + value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_OPCACHE_SERIALIZED || + value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH + ; +} + +static zend_always_inline bool zend_opcache_static_cache_block_is_free(const zend_opcache_static_cache_block *block) +{ + return (block->flags & ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE) != 0; +} + +static zend_always_inline void zend_opcache_static_cache_block_mark_free(zend_opcache_static_cache_block *block) +{ + block->flags |= ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE; +} + +static zend_always_inline void zend_opcache_static_cache_block_mark_used(zend_opcache_static_cache_block *block) +{ + block->flags &= ~ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE; + block->next_free = 0; + block->prev_free = 0; +} + +static zend_always_inline void zend_opcache_static_cache_clear_lookup_cache_context(zend_opcache_static_cache_context *context) +{ + zend_opcache_static_cache_lookup_entry *entries = context == &zend_opcache_static_cache_persistent_context_state + ? zend_opcache_static_cache_persistent_lookup_entry_storage + : zend_opcache_static_cache_volatile_lookup_entry_storage + ; + + memset(entries, 0, sizeof(zend_opcache_static_cache_lookup_entry) * ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS); +} + +static zend_always_inline void zend_opcache_static_cache_lookup_cache_reset_entry(zend_opcache_static_cache_lookup_entry *lookup_entry) +{ + if (lookup_entry == NULL) { + return; + } + + memset(lookup_entry, 0, sizeof(*lookup_entry)); +} + +static zend_always_inline void zend_opcache_static_cache_clear_lookup_caches(void) +{ + zend_opcache_static_cache_clear_lookup_cache_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_clear_lookup_cache_context(&zend_opcache_static_cache_persistent_context_state); +} + +static zend_always_inline bool *zend_opcache_static_cache_skip_publish_ptr(zend_opcache_static_cache_context *context) +{ + return context == &zend_opcache_static_cache_persistent_context_state + ? &zend_opcache_static_cache_persistent_skip_publish + : &zend_opcache_static_cache_volatile_skip_publish + ; +} + +static zend_always_inline void zend_opcache_static_cache_mark_publish_skipped(zend_opcache_static_cache_context *context) +{ + *zend_opcache_static_cache_skip_publish_ptr(context) = true; +} + +static zend_always_inline bool zend_opcache_static_cache_publish_skipped(zend_opcache_static_cache_context *context) +{ + return *zend_opcache_static_cache_skip_publish_ptr(context); +} + +static zend_always_inline void zend_opcache_static_cache_reset_publish_skips(void) +{ + zend_opcache_static_cache_volatile_skip_publish = false; + zend_opcache_static_cache_persistent_skip_publish = false; +} + +#endif /* ZEND_STATIC_CACHE_INTERNAL_H */ diff --git a/ext/opcache/zend_static_cache_shared_graph.c b/ext/opcache/zend_static_cache_shared_graph.c new file mode 100644 index 000000000000..bd8d4f5911c5 --- /dev/null +++ b/ext/opcache/zend_static_cache_shared_graph.c @@ -0,0 +1,1583 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + +#include "zend_static_cache_internal.h" +#include "zend_opcache_serializer.h" + +/* Shared-graph payloads start inside generic SHM blocks whose payload base may + * be less aligned than zend managed objects require. Align the graph start + * within the payload and treat that aligned layout as the only valid format. */ +static zend_always_inline size_t zend_opcache_static_cache_shared_graph_alignment_padding(const void *buffer) +{ + uintptr_t raw_address, aligned_address; + + raw_address = (uintptr_t) buffer; + aligned_address = (uintptr_t) ZEND_MM_ALIGNED_SIZE(raw_address); + + return (size_t) (aligned_address - raw_address); +} + +static zend_always_inline const unsigned char *zend_opcache_static_cache_shared_graph_locate_buffer( + const unsigned char *buffer, + size_t buffer_len, + size_t *graph_len +) +{ + const zend_opcache_static_cache_shared_graph_header *header; + size_t padding; + + padding = zend_opcache_static_cache_shared_graph_alignment_padding(buffer); + if (padding > buffer_len || buffer_len - padding < sizeof(zend_opcache_static_cache_shared_graph_header)) { + return NULL; + } + + buffer += padding; + buffer_len -= padding; + header = (const zend_opcache_static_cache_shared_graph_header *) buffer; + if (header->magic != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_MAGIC || + header->version != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VERSION + ) { + return NULL; + } + + if (graph_len != NULL) { + *graph_len = buffer_len; + } + + return buffer; +} + +static zend_always_inline void zend_opcache_static_cache_shared_graph_calc_init(zend_opcache_static_cache_shared_graph_calc_context *context) +{ + context->size = 0; + + zend_hash_init(&context->seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&context->seen_objects, 8, NULL, NULL, 0); +} + +static zend_always_inline void zend_opcache_static_cache_shared_graph_calc_destroy(zend_opcache_static_cache_shared_graph_calc_context *context) +{ + zend_hash_destroy(&context->seen_objects); + zend_hash_destroy(&context->seen_arrays); +} + +static zend_always_inline bool zend_opcache_static_cache_shared_graph_reserve_size(size_t *size, size_t amount) +{ + size_t aligned_amount; + + aligned_amount = ZEND_ALIGNED_SIZE(amount); + if (*size > SIZE_MAX - aligned_amount) { + return false; + } + + *size += aligned_amount; + + return *size <= UINT32_MAX; +} + +static zend_always_inline bool zend_opcache_static_cache_shared_graph_mark_seen(HashTable *seen_objects, zend_object *object) +{ + zend_ulong object_key; + + object_key = (zend_ulong) (uintptr_t) object; + if (zend_hash_index_exists(seen_objects, object_key)) { + return false; + } + + return zend_hash_index_add_empty_element(seen_objects, object_key) != NULL; +} + +static zend_always_inline bool zend_opcache_static_cache_shared_graph_mark_seen_array(HashTable *seen_arrays, const HashTable *array) +{ + zend_ulong array_key; + + array_key = (zend_ulong) (uintptr_t) array; + if (zend_hash_index_exists(seen_arrays, array_key)) { + return false; + } + + return zend_hash_index_add_empty_element(seen_arrays, array_key) != NULL; +} + +static zend_always_inline bool zend_opcache_static_cache_shared_graph_has_custom_serialization(zend_class_entry *ce) +{ + return ce->create_object != NULL || + ce->__serialize != NULL || + ce->__unserialize != NULL || + zend_hash_str_exists(&ce->function_table, ZEND_STRL("__sleep")) || + zend_hash_str_exists(&ce->function_table, ZEND_STRL("__wakeup")) + ; +} + +static zend_always_inline bool zend_opcache_static_cache_shared_graph_can_restore_direct(zend_class_entry *ce) +{ + if (ce->type != ZEND_USER_CLASS) { + return false; + } + + if (zend_opcache_serializer_find_safe_direct_cache_base(ce) != NULL) { + return false; + } + + return !zend_opcache_static_cache_shared_graph_has_custom_serialization(ce); +} + +static bool zend_opcache_static_cache_shared_graph_can_copy_direct_value(HashTable *seen_arrays, const zval *value) +{ + const HashTable *array; + const Bucket *bucket; + const zval *packed_value; + zend_ulong array_key; + uint32_t index; + bool result = true; + + switch (Z_TYPE_P(value)) { + case IS_UNDEF: + case IS_NULL: + case IS_FALSE: + case IS_TRUE: + case IS_LONG: + case IS_DOUBLE: + case IS_STRING: + return true; + case IS_ARRAY: + array = Z_ARRVAL_P(value); + if (array->nNumOfElements == 0) { + return true; + } + + if (!zend_opcache_static_cache_shared_graph_mark_seen_array(seen_arrays, array)) { + return false; + } + + array_key = (zend_ulong) (uintptr_t) array; + if (HT_IS_PACKED(array)) { + for (index = 0; index < array->nNumUsed; index++) { + packed_value = &array->arPacked[index]; + if (!zend_opcache_static_cache_shared_graph_can_copy_direct_value(seen_arrays, packed_value)) { + result = false; + break; + } + } + } else { + bucket = array->arData; + for (index = 0; index < array->nNumUsed; index++) { + if (Z_TYPE(bucket[index].val) != IS_UNDEF && + !zend_opcache_static_cache_shared_graph_can_copy_direct_value(seen_arrays, &bucket[index].val) + ) { + result = false; + break; + } + } + } + + zend_hash_index_del(seen_arrays, array_key); + + return result; + default: + return false; + } +} + +static bool zend_opcache_static_cache_shared_graph_calc_direct_value( + zend_opcache_static_cache_shared_graph_calc_context *context, + const zval *value) +{ + const HashTable *array; + const Bucket *bucket; + const zval *packed_value; + zend_ulong array_key; + uint32_t index; + size_t data_size; + bool result = true; + + switch (Z_TYPE_P(value)) { + case IS_UNDEF: + case IS_NULL: + case IS_FALSE: + case IS_TRUE: + case IS_LONG: + case IS_DOUBLE: + return true; + case IS_STRING: + return zend_opcache_static_cache_shared_graph_reserve_size( + &context->size, + _ZSTR_STRUCT_SIZE(ZSTR_LEN(Z_STR_P(value))) + ); + case IS_ARRAY: + array = Z_ARRVAL_P(value); + if (array->nNumOfElements == 0) { + return true; + } + + if (!zend_opcache_static_cache_shared_graph_mark_seen_array(&context->seen_arrays, array)) { + return false; + } + + array_key = (zend_ulong) (uintptr_t) array; + data_size = HT_IS_PACKED(array) ? HT_PACKED_USED_SIZE(array) : HT_USED_SIZE(array); + if (!zend_opcache_static_cache_shared_graph_reserve_size(&context->size, sizeof(zend_array)) || + !zend_opcache_static_cache_shared_graph_reserve_size(&context->size, data_size) + ) { + result = false; + goto direct_array_done; + } + + if (HT_IS_PACKED(array)) { + for (index = 0; index < array->nNumUsed; index++) { + packed_value = &array->arPacked[index]; + if (!zend_opcache_static_cache_shared_graph_calc_direct_value(context, packed_value)) { + result = false; + break; + } + } + } else { + bucket = array->arData; + for (index = 0; index < array->nNumUsed; index++) { + if (bucket[index].key != NULL && + !zend_opcache_static_cache_shared_graph_reserve_size( + &context->size, + _ZSTR_STRUCT_SIZE(ZSTR_LEN(bucket[index].key)) + ) + ) { + result = false; + break; + } + + if (Z_TYPE(bucket[index].val) != IS_UNDEF && + !zend_opcache_static_cache_shared_graph_calc_direct_value(context, &bucket[index].val) + ) { + result = false; + break; + } + } + } + +direct_array_done: + zend_hash_index_del(&context->seen_arrays, array_key); + + return result; + default: + return false; + } +} + +static bool zend_opcache_static_cache_shared_graph_calc_value( + zend_opcache_static_cache_shared_graph_calc_context *context, + const zval *value +) +{ + const HashTable *array; + zend_object *object; + zend_class_entry *ce, *safe_direct_base; + zend_string *key, *property_name; + zend_ulong array_key; + zval *element, *property_value, *source_value; + HashTable *properties; + uint32_t property_count; + size_t encoded_len; + bool result; + unsigned char *encoded; + + switch (Z_TYPE_P(value)) { + case IS_UNDEF: + case IS_NULL: + case IS_FALSE: + case IS_TRUE: + case IS_LONG: + case IS_DOUBLE: + return true; + case IS_STRING: + return zend_opcache_static_cache_shared_graph_reserve_size(&context->size, _ZSTR_STRUCT_SIZE(ZSTR_LEN(Z_STR_P(value)))); + case IS_ARRAY: + array = Z_ARRVAL_P(value); + + if (array->nNumOfElements == 0) { + return true; + } + + if (zend_opcache_static_cache_shared_graph_can_copy_direct_value(&context->seen_arrays, value)) { + return zend_opcache_static_cache_shared_graph_calc_direct_value(context, value); + } + + if (!zend_opcache_static_cache_shared_graph_mark_seen_array(&context->seen_arrays, array)) { + return false; + } + + array_key = (zend_ulong) (uintptr_t) array; + result = true; + + if (!zend_opcache_static_cache_shared_graph_reserve_size(&context->size, sizeof(zend_opcache_static_cache_shared_graph_array)) || + !zend_opcache_static_cache_shared_graph_reserve_size( + &context->size, + (size_t) array->nNumOfElements * sizeof(zend_opcache_static_cache_shared_graph_array_element) + ) + ) { + result = false; + + goto array_done; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL((HashTable *) array, key, element) { + if (key != NULL && !zend_opcache_static_cache_shared_graph_reserve_size(&context->size, _ZSTR_STRUCT_SIZE(ZSTR_LEN(key)))) { + result = false; + break; + } + + if (!zend_opcache_static_cache_shared_graph_calc_value(context, element)) { + result = false; + break; + } + } ZEND_HASH_FOREACH_END(); + + goto array_done; + case IS_OBJECT: + object = Z_OBJ_P(value); + ce = object->ce; + + safe_direct_base = zend_opcache_serializer_find_safe_direct_cache_base(ce); + if (safe_direct_base != NULL) { + if (zend_opcache_serializer_has_safe_direct_cache_overrides(ce, safe_direct_base)) { + return false; + } + + encoded = NULL; + encoded_len = 0; + if (!zend_opcache_serialize(&encoded, &encoded_len, value)) { + return false; + } + + efree(encoded); + + return zend_opcache_static_cache_shared_graph_reserve_size( + &context->size, + sizeof(zend_opcache_static_cache_shared_graph_serialized_object) + encoded_len - 1 + ); + } + + if (!zend_opcache_static_cache_shared_graph_can_restore_direct(ce)) { + return false; + } + + if (!zend_opcache_static_cache_shared_graph_mark_seen(&context->seen_objects, object)) { + return false; + } + + if (!zend_opcache_static_cache_shared_graph_reserve_size(&context->size, sizeof(zend_opcache_static_cache_shared_graph_object))) { + return false; + } + + if (!zend_opcache_static_cache_shared_graph_reserve_size(&context->size, _ZSTR_STRUCT_SIZE(ZSTR_LEN(ce->name)))) { + return false; + } + + properties = zend_get_properties_for((zval *) value, ZEND_PROP_PURPOSE_SERIALIZE); + property_count = properties != NULL ? properties->nNumOfElements : 0; + if (property_count != 0 && + !zend_opcache_static_cache_shared_graph_reserve_size( + &context->size, + (size_t) property_count * sizeof(zend_opcache_static_cache_shared_graph_property) + ) + ) { + if (properties != NULL) { + zend_release_properties(properties); + } + return false; + } + + if (properties != NULL) { + ZEND_HASH_FOREACH_STR_KEY_VAL(properties, property_name, property_value) { + if (property_name == NULL || !zend_opcache_static_cache_shared_graph_reserve_size(&context->size, _ZSTR_STRUCT_SIZE(ZSTR_LEN(property_name)))) { + zend_release_properties(properties); + return false; + } + + source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + if (!zend_opcache_static_cache_shared_graph_calc_value(context, source_value)) { + zend_release_properties(properties); + return false; + } + } ZEND_HASH_FOREACH_END(); + + zend_release_properties(properties); + } + + return true; + default: + return false; + } + +array_done: + zend_hash_index_del(&context->seen_arrays, array_key); + + return result; +} + +static void zend_opcache_static_cache_shared_graph_copy_init( + zend_opcache_static_cache_shared_graph_copy_context *context, + unsigned char *buffer, + size_t size +) +{ + context->buffer = buffer; + context->size = size; + context->position = 0; + + zend_hash_init(&context->seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&context->seen_objects, 8, NULL, NULL, 0); +} + +static void zend_opcache_static_cache_shared_graph_copy_destroy(zend_opcache_static_cache_shared_graph_copy_context *context) +{ + zend_hash_destroy(&context->seen_objects); + zend_hash_destroy(&context->seen_arrays); +} + +static bool zend_opcache_static_cache_shared_graph_copy_alloc( + zend_opcache_static_cache_shared_graph_copy_context *context, + size_t amount, + uint32_t *offset +) +{ + size_t aligned_amount; + + aligned_amount = ZEND_ALIGNED_SIZE(amount); + if (context->position > context->size || aligned_amount > context->size - context->position) { + return false; + } + + *offset = (uint32_t) context->position; + memset(context->buffer + context->position, 0, aligned_amount); + context->position += aligned_amount; + + return true; +} + +static bool zend_opcache_static_cache_shared_graph_copy_string( + zend_opcache_static_cache_shared_graph_copy_context *context, + const zend_string *string, + uint32_t *offset +) +{ + zend_string *new_string; + uint32_t string_offset; + size_t string_size; + + string_size = _ZSTR_STRUCT_SIZE(ZSTR_LEN(string)); + if (!zend_opcache_static_cache_shared_graph_copy_alloc(context, string_size, &string_offset)) { + return false; + } + + new_string = (zend_string *) (context->buffer + string_offset); + memcpy(new_string, string, string_size); + GC_SET_REFCOUNT(new_string, 2); + GC_TYPE_INFO(new_string) = GC_STRING | ((IS_STR_INTERNED | IS_STR_PERMANENT) << GC_FLAGS_SHIFT); + *offset = string_offset; + + return true; +} + +static bool zend_opcache_static_cache_shared_graph_copy_direct_value( + zend_opcache_static_cache_shared_graph_copy_context *context, + const zval *source, + zval *destination) +{ + const HashTable *source_array; + const Bucket *source_bucket; + const zval *source_packed; + zend_array *target; + zend_ulong array_key; + zval *target_packed; + Bucket *target_bucket; + uint32_t string_offset, array_offset, data_offset, key_offset, index; + size_t data_size; + bool result = true; + + switch (Z_TYPE_P(source)) { + case IS_UNDEF: + ZVAL_UNDEF(destination); + return true; + case IS_NULL: + ZVAL_NULL(destination); + return true; + case IS_TRUE: + ZVAL_TRUE(destination); + return true; + case IS_FALSE: + ZVAL_FALSE(destination); + return true; + case IS_LONG: + ZVAL_LONG(destination, Z_LVAL_P(source)); + return true; + case IS_DOUBLE: + ZVAL_DOUBLE(destination, Z_DVAL_P(source)); + return true; + case IS_STRING: + if (!zend_opcache_static_cache_shared_graph_copy_string(context, Z_STR_P(source), &string_offset)) { + return false; + } + + ZVAL_INTERNED_STR(destination, (zend_string *) (void *) (context->buffer + string_offset)); + return true; + case IS_ARRAY: + source_array = Z_ARRVAL_P(source); + if (source_array->nNumOfElements == 0) { + ZVAL_EMPTY_ARRAY(destination); + return true; + } + + if (!zend_opcache_static_cache_shared_graph_mark_seen_array(&context->seen_arrays, source_array)) { + return false; + } + + array_key = (zend_ulong) (uintptr_t) source_array; + data_size = HT_IS_PACKED(source_array) ? HT_PACKED_USED_SIZE(source_array) : HT_USED_SIZE(source_array); + if (!zend_opcache_static_cache_shared_graph_copy_alloc(context, sizeof(zend_array), &array_offset) || + !zend_opcache_static_cache_shared_graph_copy_alloc(context, data_size, &data_offset) + ) { + result = false; + goto copy_direct_array_done; + } + + target = (zend_array *) (context->buffer + array_offset); + memcpy(target, source_array, sizeof(zend_array)); + memcpy(context->buffer + data_offset, HT_GET_DATA_ADDR(source_array), data_size); + GC_SET_REFCOUNT(target, 2); + GC_TYPE_INFO(target) = GC_ARRAY | ((IS_ARRAY_IMMUTABLE | GC_NOT_COLLECTABLE) << GC_FLAGS_SHIFT); + HT_FLAGS(target) |= HASH_FLAG_STATIC_KEYS; + target->pDestructor = NULL; + target->nInternalPointer = 0; + HT_SET_DATA_ADDR(target, context->buffer + data_offset); + + if (HT_IS_PACKED(source_array)) { + target_packed = target->arPacked; + for (index = 0; index < source_array->nNumUsed; index++) { + source_packed = &source_array->arPacked[index]; + if (!zend_opcache_static_cache_shared_graph_copy_direct_value(context, source_packed, &target_packed[index])) { + result = false; + break; + } + } + } else { + source_bucket = source_array->arData; + target_bucket = target->arData; + for (index = 0; index < source_array->nNumUsed; index++) { + if (source_bucket[index].key != NULL) { + if (!zend_opcache_static_cache_shared_graph_copy_string(context, source_bucket[index].key, &key_offset)) { + result = false; + break; + } + + target_bucket[index].key = (zend_string *) (void *) (context->buffer + key_offset); + } else { + target_bucket[index].key = NULL; + } + + if (!zend_opcache_static_cache_shared_graph_copy_direct_value(context, &source_bucket[index].val, &target_bucket[index].val)) { + result = false; + break; + } + } + } + +copy_direct_array_done: + zend_hash_index_del(&context->seen_arrays, array_key); + if (!result) { + return false; + } + + ZVAL_ARR(destination, (zend_array *) (void *) (context->buffer + array_offset)); + Z_TYPE_FLAGS_P(destination) = 0; + return true; + default: + return false; + } +} + +static bool zend_opcache_static_cache_shared_graph_copy_property_value( + zend_opcache_static_cache_shared_graph_copy_context *context, + const zval *source, + zend_opcache_static_cache_shared_graph_value *destination +) +{ + zend_opcache_static_cache_shared_graph_serialized_object *serialized_object; + zend_opcache_static_cache_shared_graph_object *graph_object; + zend_opcache_static_cache_shared_graph_property *graph_properties; + zend_opcache_static_cache_shared_graph_array *graph_array; + zend_opcache_static_cache_shared_graph_array_element *graph_elements, *graph_element; + zend_object *object; + zend_class_entry *ce, *safe_direct_base; + zend_string *property_name, *key; + zend_ulong array_key, h; + zval *property_value, *source_value, *element, array_value; + HashTable *properties; + uint32_t payload_offset, string_offset, object_offset, class_name_offset, properties_offset, property_index, property_count, array_offset, elements_offset, key_offset; + size_t encoded_len, serialized_size; + bool result; + unsigned char *encoded; + + memset(destination, 0, sizeof(*destination)); + + switch (Z_TYPE_P(source)) { + case IS_UNDEF: + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_UNDEF; + + return true; + case IS_NULL: + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_NULL; + + return true; + case IS_TRUE: + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_TRUE; + + return true; + case IS_FALSE: + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_FALSE; + + return true; + case IS_LONG: + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_LONG; + destination->payload.long_value = Z_LVAL_P(source); + + return true; + case IS_DOUBLE: + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DOUBLE; + destination->payload.double_value = Z_DVAL_P(source); + + return true; + case IS_STRING: + if (!zend_opcache_static_cache_shared_graph_copy_string(context, Z_STR_P(source), &string_offset)) { + return false; + } + + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_STRING; + destination->payload.offset = string_offset; + + return true; + case IS_ARRAY: { + result = true; + + if (Z_ARRVAL_P(source)->nNumOfElements == 0) { + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_ARRAY; + destination->payload.offset = 0; + + return true; + } + + if (zend_opcache_static_cache_shared_graph_can_copy_direct_value(&context->seen_arrays, source)) { + if (!zend_opcache_static_cache_shared_graph_copy_direct_value(context, source, &array_value)) { + return false; + } + + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_ARRAY; + destination->payload.offset = (uint32_t) ((unsigned char *) Z_ARRVAL(array_value) - context->buffer); + + return true; + } + + if (!zend_opcache_static_cache_shared_graph_mark_seen_array(&context->seen_arrays, Z_ARRVAL_P(source))) { + return false; + } + + array_key = (zend_ulong) (uintptr_t) Z_ARRVAL_P(source); + if (!zend_opcache_static_cache_shared_graph_copy_alloc(context, sizeof(*graph_array), &array_offset) || + !zend_opcache_static_cache_shared_graph_copy_alloc( + context, + (size_t) Z_ARRVAL_P(source)->nNumOfElements * sizeof(*graph_elements), + &elements_offset + ) + ) { + result = false; + + goto array_done; + } + + graph_array = (zend_opcache_static_cache_shared_graph_array *) (context->buffer + array_offset); + graph_array->count = Z_ARRVAL_P(source)->nNumOfElements; + graph_array->next_free = (uint32_t) Z_ARRVAL_P(source)->nNextFreeElement; + graph_array->elements_offset = elements_offset; + graph_elements = (zend_opcache_static_cache_shared_graph_array_element *) (context->buffer + elements_offset); + graph_element = graph_elements; + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(source), h, key, element) { + memset(graph_element, 0, sizeof(*graph_element)); + graph_element->h = h; + if (key != NULL) { + if (!zend_opcache_static_cache_shared_graph_copy_string(context, key, &key_offset)) { + result = false; + break; + } + + graph_element->key_offset = key_offset; + } + + if (!zend_opcache_static_cache_shared_graph_copy_property_value(context, element, &graph_element->value)) { + result = false; + break; + } + + ++graph_element; + } ZEND_HASH_FOREACH_END(); + + if (result) { + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DYNAMIC_ARRAY; + destination->payload.offset = array_offset; + } + + goto array_done; + } + case IS_OBJECT: + ce = Z_OBJCE_P(source); + safe_direct_base = zend_opcache_serializer_find_safe_direct_cache_base(ce); + if (safe_direct_base != NULL) { + if (zend_opcache_serializer_has_safe_direct_cache_overrides(ce, safe_direct_base)) { + return false; + } + + encoded = NULL; + encoded_len = 0; + if (!zend_opcache_serialize(&encoded, &encoded_len, source)) { + return false; + } + + serialized_size = sizeof(zend_opcache_static_cache_shared_graph_serialized_object) + encoded_len - 1; + if (!zend_opcache_static_cache_shared_graph_copy_alloc(context, serialized_size, &payload_offset)) { + efree(encoded); + + return false; + } + + serialized_object = (zend_opcache_static_cache_shared_graph_serialized_object *) (context->buffer + payload_offset); + serialized_object->data_len = (uint32_t) encoded_len; + memcpy(serialized_object->data, encoded, encoded_len); + efree(encoded); + + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_SERIALIZED_OBJECT; + destination->payload.offset = payload_offset; + + return true; + } + + object = Z_OBJ_P(source); + if (!zend_opcache_static_cache_shared_graph_can_restore_direct(object->ce)) { + return false; + } + + if (!zend_opcache_static_cache_shared_graph_mark_seen(&context->seen_objects, object)) { + return false; + } + + if (!zend_opcache_static_cache_shared_graph_copy_alloc(context, sizeof(*graph_object), &object_offset)) { + return false; + } + + if (!zend_opcache_static_cache_shared_graph_copy_string(context, object->ce->name, &class_name_offset)) { + return false; + } + + properties = zend_get_properties_for((zval *) source, ZEND_PROP_PURPOSE_SERIALIZE); + property_count = properties != NULL ? properties->nNumOfElements : 0; + properties_offset = 0; + if (property_count != 0 && + !zend_opcache_static_cache_shared_graph_copy_alloc( + context, + ((size_t) property_count * sizeof(zend_opcache_static_cache_shared_graph_property)), + &properties_offset + ) + ) { + if (properties != NULL) { + zend_release_properties(properties); + } + return false; + } + + graph_object = (zend_opcache_static_cache_shared_graph_object *) (context->buffer + object_offset); + graph_object->class_name_offset = class_name_offset; + graph_object->property_count = property_count; + graph_object->properties_offset = properties_offset; + + if (property_count == 0) { + if (properties != NULL) { + zend_release_properties(properties); + } + + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT; + destination->payload.offset = object_offset; + + return true; + } + + graph_properties = (zend_opcache_static_cache_shared_graph_property *) (context->buffer + properties_offset); + property_index = 0; + ZEND_HASH_FOREACH_STR_KEY_VAL(properties, property_name, property_value) { + if (property_name == NULL || + !zend_opcache_static_cache_shared_graph_copy_string(context, property_name, &graph_properties[property_index].name_offset) + ) { + zend_release_properties(properties); + + return false; + } + + source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + if (!zend_opcache_static_cache_shared_graph_copy_property_value( + context, + source_value, + &graph_properties[property_index].value + ) + ) { + zend_release_properties(properties); + + return false; + } + + ++property_index; + } ZEND_HASH_FOREACH_END(); + + zend_release_properties(properties); + destination->type = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT; + destination->payload.offset = object_offset; + + return true; + default: + return false; + } + +array_done: + zend_hash_index_del(&context->seen_arrays, array_key); + + return result; +} + +static bool zend_opcache_static_cache_shared_graph_try_update_declared_object_property( + zend_object *object, + zend_string *property_name, + zend_property_info *property_info, + zval *property_value, + bool *failed +) +{ + zval *slot, tmp, indirect; + + *failed = false; + + if (property_info == NULL || + property_info == ZEND_WRONG_PROPERTY_INFO || + !zend_string_equals(property_info->name, property_name) || + (property_info->flags & (ZEND_ACC_STATIC|ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK|ZEND_ACC_VIRTUAL)) != 0 || + (property_info->flags & ZEND_ACC_PPP_MASK) != ZEND_ACC_PUBLIC || + property_info->offset == ZEND_VIRTUAL_PROPERTY_OFFSET + ) { + return false; + } + + slot = OBJ_PROP(object, property_info->offset); + ZVAL_COPY_DEREF(&tmp, property_value); + + if (ZEND_TYPE_IS_SET(property_info->type) && + !zend_verify_property_type(property_info, &tmp, true) + ) { + zval_ptr_dtor(&tmp); + + *failed = true; + + return false; + } + + zval_ptr_dtor(slot); + ZVAL_COPY_VALUE(slot, &tmp); + + if (object->properties != NULL) { + ZVAL_INDIRECT(&indirect, slot); + zend_hash_update(object->properties, property_name, &indirect); + } + + return true; +} + +static bool zend_opcache_static_cache_shared_graph_update_object_property( + zval *object_zv, + zend_string *property_name, + zval *property_value +) +{ + zend_object *object; + zend_property_info *property_info; + bool failed; + + object = Z_OBJ_P(object_zv); + if (ZSTR_LEN(property_name) != 0 && ZSTR_VAL(property_name)[0] != '\0') { + property_info = zend_get_property_info(object->ce, property_name, true); + if (zend_opcache_static_cache_shared_graph_try_update_declared_object_property( + object, + property_name, + property_info, + property_value, + &failed) + ) { + return true; + } + + if (failed) { + return false; + } + } + + return zend_opcache_serializer_update_object_property(object, property_name, property_value); +} + +static bool zend_opcache_static_cache_shared_graph_update_object_property_at( + zval *object_zv, + zend_string *property_name, + uint32_t property_index, + zval *property_value +) +{ + zend_object *object; + zend_property_info *property_info; + bool failed; + + object = Z_OBJ_P(object_zv); + if (ZSTR_LEN(property_name) != 0 && ZSTR_VAL(property_name)[0] != '\0' && + object->ce->type == ZEND_USER_CLASS && + object->ce->properties_info_table != NULL && + property_index < object->ce->default_properties_count + ) { + property_info = object->ce->properties_info_table[property_index]; + + if (zend_opcache_static_cache_shared_graph_try_update_declared_object_property( + object, + property_name, + property_info, + property_value, + &failed) + ) { + return true; + } + + if (failed) { + return false; + } + } + + return zend_opcache_static_cache_shared_graph_update_object_property(object_zv, property_name, property_value); +} + +static bool zend_opcache_static_cache_fetch_shared_graph_value( + const unsigned char *buffer, + const zend_opcache_static_cache_shared_graph_value *value, + zval *destination +) +{ + const zend_opcache_static_cache_shared_graph_object *graph_object; + const zend_opcache_static_cache_shared_graph_array *graph_array; + const zend_opcache_static_cache_shared_graph_array_element *graph_elements, *graph_element; + const zend_opcache_static_cache_shared_graph_property *properties, *property; + const zend_opcache_static_cache_shared_graph_serialized_object *serialized_object; + zend_class_entry *ce; + zend_string *class_name, *property_name; + zval property_value; + uint32_t index; + + switch (value->type) { + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_UNDEF: + ZVAL_UNDEF(destination); + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_NULL: + ZVAL_NULL(destination); + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_TRUE: + ZVAL_TRUE(destination); + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_FALSE: + ZVAL_FALSE(destination); + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_LONG: + ZVAL_LONG(destination, value->payload.long_value); + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DOUBLE: + ZVAL_DOUBLE(destination, value->payload.double_value); + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_STRING: + ZVAL_INTERNED_STR(destination, (zend_string *) (void *) (buffer + (uint32_t) value->payload.offset)); + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_ARRAY: + if ((uint32_t) value->payload.offset == 0) { + ZVAL_EMPTY_ARRAY(destination); + } else { + ZVAL_ARR(destination, (zend_array *) (void *) (buffer + (uint32_t) value->payload.offset)); + Z_TYPE_FLAGS_P(destination) = 0; + } + + zend_opcache_static_cache_capture_decoded_value(destination); + + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DYNAMIC_ARRAY: + graph_array = (const zend_opcache_static_cache_shared_graph_array *) (buffer + (uint32_t) value->payload.offset); + array_init_size(destination, graph_array->count); + graph_elements = (const zend_opcache_static_cache_shared_graph_array_element *) (buffer + graph_array->elements_offset); + + for (index = 0; index < graph_array->count; index++) { + graph_element = &graph_elements[index]; + ZVAL_UNDEF(&property_value); + if (!zend_opcache_static_cache_fetch_shared_graph_value(buffer, &graph_element->value, &property_value)) { + zval_ptr_dtor(destination); + ZVAL_UNDEF(destination); + + return false; + } + + if (graph_element->key_offset != 0) { + property_name = (zend_string *) (void *) (buffer + graph_element->key_offset); + if (zend_hash_add_new(Z_ARRVAL_P(destination), property_name, &property_value) == NULL) { + zval_ptr_dtor(&property_value); + zval_ptr_dtor(destination); + ZVAL_UNDEF(destination); + + return false; + } + } else if (zend_hash_index_add_new(Z_ARRVAL_P(destination), graph_element->h, &property_value) == NULL) { + zval_ptr_dtor(&property_value); + zval_ptr_dtor(destination); + ZVAL_UNDEF(destination); + + return false; + } + } + + Z_ARRVAL_P(destination)->nNextFreeElement = graph_array->next_free; + zend_opcache_static_cache_capture_decoded_value(destination); + + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT: + graph_object = (const zend_opcache_static_cache_shared_graph_object *) (buffer + (uint32_t) value->payload.offset); + class_name = (zend_string *) (void *) (buffer + graph_object->class_name_offset); + ce = zend_lookup_class(class_name); + if (ce == NULL || object_init_ex(destination, ce) != SUCCESS) { + return false; + } + + if (graph_object->property_count == 0) { + zend_opcache_static_cache_capture_decoded_value(destination); + return true; + } + + properties = (const zend_opcache_static_cache_shared_graph_property *) (buffer + graph_object->properties_offset); + for (index = 0; index < graph_object->property_count; index++) { + property = &properties[index]; + if (property->value.type == ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_UNDEF) { + continue; + } + + property_name = (zend_string *) (void *) (buffer + property->name_offset); + ZVAL_UNDEF(&property_value); + if (!zend_opcache_static_cache_fetch_shared_graph_value(buffer, &property->value, &property_value) || + !zend_opcache_static_cache_shared_graph_update_object_property_at(destination, property_name, index, &property_value) + ) { + zval_ptr_dtor(&property_value); + zval_ptr_dtor(destination); + ZVAL_UNDEF(destination); + + return false; + } + + zval_ptr_dtor(&property_value); + } + + zend_opcache_static_cache_capture_decoded_value(destination); + + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_SERIALIZED_OBJECT: + serialized_object = (const zend_opcache_static_cache_shared_graph_serialized_object *) (buffer + (uint32_t) value->payload.offset); + + return zend_opcache_static_cache_capture_active + ? zend_opcache_unserialize_ex( + destination, + serialized_object->data, + serialized_object->data_len, + zend_opcache_static_cache_capture_decoded_value, + zend_opcache_static_cache_capture_decoded_reachable_value) + : zend_opcache_unserialize(destination, serialized_object->data, serialized_object->data_len) + ; + default: + return false; + } +} + +static zend_opcache_static_cache_shared_graph_header *zend_opcache_static_cache_shared_graph_payload_header(uint32_t payload_offset) +{ + const unsigned char *graph_buffer; + zend_opcache_static_cache_shared_graph_header *header; + size_t buffer_len; + + if (payload_offset == 0) { + return NULL; + } + + buffer_len = zend_opcache_static_cache_block_payload_capacity(payload_offset); + if (buffer_len == 0) { + return NULL; + } + + graph_buffer = zend_opcache_static_cache_shared_graph_locate_buffer( + (const unsigned char *) zend_opcache_static_cache_ptr(payload_offset), + buffer_len, + NULL + ); + if (graph_buffer == NULL) { + return NULL; + } + + header = (zend_opcache_static_cache_shared_graph_header *) graph_buffer; + if (header->magic != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_MAGIC || + header->version != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VERSION + ) { + return NULL; + } + + return header; +} + +static void zend_opcache_static_cache_free_retired_shared_graphs(void) +{ + zend_opcache_static_cache_shared_graph_ref *ref; + zend_opcache_static_cache_context *previous_context; + uint32_t index; + + if (zend_opcache_static_cache_retired_shared_graph_count == 0) { + return; + } + + for (index = 0; index < zend_opcache_static_cache_retired_shared_graph_count; index++) { + ref = &zend_opcache_static_cache_retired_shared_graphs[index]; + + if (ref->context == NULL) { + continue; + } + + previous_context = zend_opcache_static_cache_activate_context(ref->context); + if (zend_opcache_static_cache_wlock()) { + if (zend_opcache_static_cache_header_is_initialized_locked()) { + zend_opcache_static_cache_free_locked(ref->payload_offset); + } + + zend_opcache_static_cache_unlock(); + } + + zend_opcache_static_cache_restore_context(previous_context); + } + + efree(zend_opcache_static_cache_retired_shared_graphs); + + zend_opcache_static_cache_retired_shared_graphs = NULL; + zend_opcache_static_cache_retired_shared_graph_count = 0; + zend_opcache_static_cache_retired_shared_graph_capacity = 0; +} + +bool zend_opcache_static_cache_calculate_shared_graph_size( + const zval *value, + size_t *buffer_len +) +{ + zend_opcache_static_cache_shared_graph_calc_context calc_context; + bool result; + + if (!buffer_len) { + return false; + } + + *buffer_len = 0; + if (Z_TYPE_P(value) == IS_OBJECT) { + if (!zend_opcache_static_cache_shared_graph_can_restore_direct(Z_OBJCE_P(value))) { + return false; + } + } else if (Z_TYPE_P(value) != IS_ARRAY) { + return false; + } + + zend_opcache_static_cache_shared_graph_calc_init(&calc_context); + + result = zend_opcache_static_cache_shared_graph_reserve_size(&calc_context.size, sizeof(zend_opcache_static_cache_shared_graph_header)); + if (result) { + result = zend_opcache_static_cache_shared_graph_calc_value(&calc_context, value); + } + if (result) { + if (calc_context.size > UINT32_MAX - (ZEND_MM_ALIGNMENT - 1)) { + result = false; + } else { + calc_context.size += ZEND_MM_ALIGNMENT - 1; + } + } + if (result) { + *buffer_len = calc_context.size; + } + + zend_opcache_static_cache_shared_graph_calc_destroy(&calc_context); + + return result; +} + +bool zend_opcache_static_cache_build_shared_graph_in_place( + const zval *value, + unsigned char *buffer, + size_t buffer_len, + size_t *graph_len +) +{ + zend_opcache_static_cache_shared_graph_copy_context copy_context; + zend_opcache_static_cache_shared_graph_header *header; + zend_opcache_static_cache_shared_graph_value root_value; + uint32_t header_offset, root_offset, root_type; + size_t padding; + bool result; + + if (buffer == NULL) { + return false; + } + + padding = zend_opcache_static_cache_shared_graph_alignment_padding(buffer); + if (padding > buffer_len || buffer_len - padding < sizeof(zend_opcache_static_cache_shared_graph_header)) { + return false; + } + if (padding != 0) { + memset(buffer, 0, padding); + } + + buffer += padding; + buffer_len -= padding; + + zend_opcache_static_cache_shared_graph_copy_init(©_context, buffer, buffer_len); + root_offset = 0; + root_type = 0; + result = zend_opcache_static_cache_shared_graph_copy_alloc(©_context, sizeof(*header), &header_offset) && header_offset == 0; + if (result) { + if (Z_TYPE_P(value) == IS_OBJECT) { + result = zend_opcache_static_cache_shared_graph_copy_property_value(©_context, value, &root_value) + && root_value.type == ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT + ; + } else if (Z_TYPE_P(value) == IS_ARRAY) { + result = zend_opcache_static_cache_shared_graph_copy_property_value(©_context, value, &root_value) && + (root_value.type == ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_ARRAY || + root_value.type == ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DYNAMIC_ARRAY) + ; + } else { + result = false; + } + + if (result) { + root_type = root_value.type; + root_offset = (uint32_t) root_value.payload.offset; + } + } + + if (!result) { + zend_opcache_static_cache_shared_graph_copy_destroy(©_context); + + return false; + } + + header = (zend_opcache_static_cache_shared_graph_header *) buffer; + header->magic = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_MAGIC; + header->version = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VERSION; + header->root_offset = root_offset; + header->root_type = root_type; + ZEND_ATOMIC_INT_INIT(&header->ref_state, 0); + if (graph_len != NULL) { + *graph_len = copy_context.position; + } + + zend_opcache_static_cache_shared_graph_copy_destroy(©_context); + + return true; +} + +bool zend_opcache_static_cache_shared_graph_copy_fits_buffer( + const unsigned char *source_buffer, + size_t source_buffer_len, + size_t source_graph_len, + const unsigned char *target_buffer, + size_t target_buffer_len +) +{ + size_t target_padding; + + if (source_buffer == NULL || target_buffer == NULL || source_graph_len == 0 || source_graph_len > source_buffer_len) { + return false; + } + + target_padding = zend_opcache_static_cache_shared_graph_alignment_padding(target_buffer); + return target_padding <= target_buffer_len && source_graph_len <= target_buffer_len - target_padding; +} + +bool zend_opcache_static_cache_fetch_shared_graph( + const unsigned char *buffer, + size_t buffer_len, + zval *destination +) +{ + const zend_opcache_static_cache_shared_graph_header *header; + const unsigned char *graph_buffer; + zend_opcache_static_cache_shared_graph_value root_value; + uint32_t root_type; + bool capture_active, result; + + graph_buffer = zend_opcache_static_cache_shared_graph_locate_buffer(buffer, buffer_len, &buffer_len); + if (graph_buffer == NULL) { + return false; + } + + buffer = graph_buffer; + + header = (const zend_opcache_static_cache_shared_graph_header *) buffer; + if (header->magic != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_MAGIC || + header->version != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VERSION || + (header->root_offset != 0 && header->root_offset >= buffer_len) + ) { + return false; + } + + root_type = header->root_type != 0 ? header->root_type : ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT; + memset(&root_value, 0, sizeof(root_value)); + root_value.type = (uint8_t) root_type; + root_value.payload.offset = header->root_offset; + capture_active = zend_opcache_static_cache_capture_active; + if (capture_active) { + zend_opcache_static_cache_capture_active = false; + } + + switch (root_type) { + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT: + if (header->root_offset == 0) { + if (capture_active) { + zend_opcache_static_cache_capture_active = true; + } + + return false; + } + + result = zend_opcache_static_cache_fetch_shared_graph_value(buffer, &root_value, destination); + break; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_ARRAY: + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DYNAMIC_ARRAY: + result = zend_opcache_static_cache_fetch_shared_graph_value(buffer, &root_value, destination); + break; + default: + if (capture_active) { + zend_opcache_static_cache_capture_active = true; + } + + return false; + } + + if (capture_active) { + zend_opcache_static_cache_capture_active = true; + if (result) { + zend_opcache_static_cache_capture_decoded_reachable_value(destination); + } + } + + return result; +} + +bool zend_opcache_static_cache_shared_graph_can_overwrite_payload_locked(uint32_t payload_offset) +{ + zend_opcache_static_cache_shared_graph_header *header = zend_opcache_static_cache_shared_graph_payload_header(payload_offset); + + if (header == NULL) { + return false; + } + + return zend_atomic_int_load_ex(&header->ref_state) == 0; +} + +bool zend_opcache_static_cache_shared_graph_acquire_locked(uint32_t payload_offset) +{ + zend_opcache_static_cache_shared_graph_header *header = zend_opcache_static_cache_shared_graph_payload_header(payload_offset); + int state, refcount, expected; + + if (header == NULL) { + return false; + } + + for (;;) { + state = zend_atomic_int_load_ex(&header->ref_state); + refcount = state & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_REFCOUNT_MASK; + expected = state; + + if ((state & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_RETIRED) != 0 || + refcount == ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_REFCOUNT_MASK + ) { + return false; + } + + if (zend_atomic_int_compare_exchange_ex(&header->ref_state, &expected, state + 1)) { + return true; + } + } +} + +bool zend_opcache_static_cache_shared_graph_retire_payload_locked(uint32_t payload_offset) +{ + zend_opcache_static_cache_shared_graph_header *header = zend_opcache_static_cache_shared_graph_payload_header(payload_offset); + int state, refcount, expected; + + if (header == NULL) { + return true; + } + + for (;;) { + state = zend_atomic_int_load_ex(&header->ref_state); + refcount = state & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_REFCOUNT_MASK; + expected = state; + + if (refcount == 0) { + return true; + } + + if ((state & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_RETIRED) != 0) { + return false; + } + + if (zend_atomic_int_compare_exchange_ex(&header->ref_state, &expected, state | ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_RETIRED)) { + return false; + } + } +} + +bool zend_opcache_static_cache_shared_graph_release_ref_locked(uint32_t payload_offset) +{ + zend_opcache_static_cache_shared_graph_header *header = zend_opcache_static_cache_shared_graph_payload_header(payload_offset); + int state, refcount, expected, desired; + + if (header == NULL) { + return false; + } + + for (;;) { + state = zend_atomic_int_load_ex(&header->ref_state); + refcount = state & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_REFCOUNT_MASK; + expected = state; + + if (refcount == 0) { + return false; + } + + desired = (state & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_RETIRED) | (refcount - 1); + if (zend_atomic_int_compare_exchange_ex(&header->ref_state, &expected, desired)) { + return (desired & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_RETIRED) != 0 && + (desired & ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_REFCOUNT_MASK) == 0 + ; + } + } +} + +bool zend_opcache_static_cache_has_request_shared_graph_ref(uint32_t payload_offset) +{ + zend_opcache_static_cache_context *context; + uint32_t index; + + context = zend_opcache_static_cache_active_context(); + for (index = 0; index < zend_opcache_static_cache_shared_graph_ref_count; index++) { + if (zend_opcache_static_cache_shared_graph_refs[index].context == context && + zend_opcache_static_cache_shared_graph_refs[index].payload_offset == payload_offset + ) { + return true; + } + } + + return false; +} + +void zend_opcache_static_cache_register_shared_graph_ref(uint32_t payload_offset) +{ + if (zend_opcache_static_cache_shared_graph_ref_count == zend_opcache_static_cache_shared_graph_ref_capacity) { + zend_opcache_static_cache_shared_graph_ref_capacity = zend_opcache_static_cache_shared_graph_ref_capacity == 0 + ? 8 + : zend_opcache_static_cache_shared_graph_ref_capacity * 2 + ; + + zend_opcache_static_cache_shared_graph_refs = erealloc( + zend_opcache_static_cache_shared_graph_refs, + sizeof(zend_opcache_static_cache_shared_graph_ref) * zend_opcache_static_cache_shared_graph_ref_capacity + ); + } + + zend_opcache_static_cache_shared_graph_refs[zend_opcache_static_cache_shared_graph_ref_count].context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_shared_graph_refs[zend_opcache_static_cache_shared_graph_ref_count].payload_offset = payload_offset; + zend_opcache_static_cache_shared_graph_ref_count++; +} + +void zend_opcache_static_cache_defer_retired_shared_graph_free(uint32_t payload_offset) +{ + zend_opcache_static_cache_shared_graph_ref *ref; + + if (zend_opcache_static_cache_retired_shared_graph_count == zend_opcache_static_cache_retired_shared_graph_capacity) { + zend_opcache_static_cache_retired_shared_graph_capacity = zend_opcache_static_cache_retired_shared_graph_capacity == 0 + ? 4 + : zend_opcache_static_cache_retired_shared_graph_capacity * 2 + ; + + zend_opcache_static_cache_retired_shared_graphs = erealloc( + zend_opcache_static_cache_retired_shared_graphs, + sizeof(zend_opcache_static_cache_shared_graph_ref) * zend_opcache_static_cache_retired_shared_graph_capacity + ); + } + + ref = &zend_opcache_static_cache_retired_shared_graphs[zend_opcache_static_cache_retired_shared_graph_count++]; + ref->context = zend_opcache_static_cache_active_context(); + ref->payload_offset = payload_offset; +} + +void zend_opcache_static_cache_release_request_shared_graph_refs(void) +{ + zend_opcache_static_cache_shared_graph_ref *ref; + zend_opcache_static_cache_context *previous_context; + uint32_t index; + + if (zend_opcache_static_cache_shared_graph_ref_count == 0) { + zend_opcache_static_cache_free_retired_shared_graphs(); + + return; + } + + for (index = 0; index < zend_opcache_static_cache_shared_graph_ref_count; index++) { + ref = &zend_opcache_static_cache_shared_graph_refs[index]; + + if (ref->context == NULL || !zend_opcache_static_cache_context_runtime(ref->context)->available) { + continue; + } + + previous_context = zend_opcache_static_cache_activate_context(ref->context); + if (zend_opcache_static_cache_wlock()) { + if (zend_opcache_static_cache_header_is_initialized_locked() && + zend_opcache_static_cache_shared_graph_release_ref_locked(ref->payload_offset) + ) { + zend_opcache_static_cache_free_locked(ref->payload_offset); + } + + zend_opcache_static_cache_unlock(); + } + + zend_opcache_static_cache_restore_context(previous_context); + } + + efree(zend_opcache_static_cache_shared_graph_refs); + + zend_opcache_static_cache_shared_graph_refs = NULL; + zend_opcache_static_cache_shared_graph_ref_count = 0; + zend_opcache_static_cache_shared_graph_ref_capacity = 0; + + zend_opcache_static_cache_free_retired_shared_graphs(); +} diff --git a/ext/opcache/zend_static_cache_statics.c b/ext/opcache/zend_static_cache_statics.c new file mode 100644 index 000000000000..8366d580eb20 --- /dev/null +++ b/ext/opcache/zend_static_cache_statics.c @@ -0,0 +1,3717 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + +#include "zend_static_cache_internal.h" +#include "zend_opcache_serializer.h" + +typedef struct _zend_opcache_static_cache_prepare_memo_entry { + zval root_snapshot; + unsigned char *payload_buffer; + size_t payload_size; + size_t payload_used_size; + uint32_t value_len; + uint8_t root_type; + bool dirty; +} zend_opcache_static_cache_prepare_memo_entry; + +typedef struct _zend_opcache_static_cache_prepare_memo_dependency { + zend_opcache_static_cache_prepare_memo_entry *single_entry; + HashTable *entries; +} zend_opcache_static_cache_prepare_memo_dependency; + +static ZEND_EXT_TLS HashTable *zend_opcache_static_cache_prepare_memo_entries = NULL; +static ZEND_EXT_TLS HashTable *zend_opcache_static_cache_prepare_memo_array_roots = NULL; +static ZEND_EXT_TLS HashTable *zend_opcache_static_cache_prepare_memo_object_roots = NULL; +static ZEND_EXT_TLS HashTable *zend_opcache_static_cache_prepare_memo_arrays = NULL; +static ZEND_EXT_TLS HashTable *zend_opcache_static_cache_prepare_memo_objects = NULL; +static ZEND_EXT_TLS bool zend_opcache_static_cache_has_prepare_memo_arrays = false; +static ZEND_EXT_TLS bool zend_opcache_static_cache_has_prepare_memo_objects = false; + +static zend_always_inline HashTable *zend_opcache_static_cache_capture_ensure_table(HashTable **table_ptr) +{ + if (*table_ptr == NULL) { + *table_ptr = emalloc(sizeof(HashTable)); + zend_hash_init(*table_ptr, 8, NULL, NULL, 0); + } + + return *table_ptr; +} + +static zend_always_inline void zend_opcache_static_cache_capture_clear(void) +{ + zend_opcache_static_cache_capture_handle = NULL; + + if (zend_opcache_static_cache_capture_arrays != NULL) { + zend_hash_clean(zend_opcache_static_cache_capture_arrays); + } + + if (zend_opcache_static_cache_capture_objects != NULL) { + zend_hash_clean(zend_opcache_static_cache_capture_objects); + } + + zend_opcache_static_cache_capture_available = false; +} + +static void zend_opcache_static_cache_prepare_memo_entry_dtor(zval *zv) +{ + zend_opcache_static_cache_prepare_memo_entry *entry = Z_PTR_P(zv); + + if (entry == NULL) { + return; + } + + if (entry->payload_buffer != NULL) { + efree(entry->payload_buffer); + } + + zval_ptr_dtor(&entry->root_snapshot); + efree(entry); +} + +static void zend_opcache_static_cache_prepare_memo_dependency_dtor(zval *zv) +{ + zend_opcache_static_cache_prepare_memo_dependency *dependency = Z_PTR_P(zv); + + if (dependency == NULL) { + return; + } + + if (dependency->entries != NULL) { + zend_hash_destroy(dependency->entries); + efree(dependency->entries); + } + + efree(dependency); +} + +static zend_opcache_static_cache_kind zend_opcache_static_cache_kind_from_volatile_static_attribute( + zend_attribute *volatile_static, + zend_class_entry *scope, + zend_long *ttl) +{ + zend_opcache_static_cache_volatile_static_attribute_config config; + zend_string *error = NULL; + + if (ttl != NULL) { + *ttl = 0; + } + + if (volatile_static == NULL) { + return ZEND_OPCACHE_STATIC_CACHE_NONE; + } + + if (!zend_opcache_static_cache_volatile_static_attribute_config_from_attribute(volatile_static, scope, &config, &error)) { + zend_error(E_ERROR, "%s", ZSTR_VAL(error)); + + return ZEND_OPCACHE_STATIC_CACHE_NONE; + } + + if (ttl != NULL) { + *ttl = config.ttl; + } + + return config.strategy == ZEND_OPCACHE_STATIC_CACHE_STRATEGY_TRACKING + ? ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING + : ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC + ; +} + +static zend_opcache_static_cache_kind zend_opcache_static_cache_kind_from_attributes( + const HashTable *attributes, + zend_class_entry *scope, + zend_long *ttl) +{ + bool persistent_static; + zend_attribute *volatile_static; + + if (ttl != NULL) { + *ttl = 0; + } + + if (attributes == NULL) { + return ZEND_OPCACHE_STATIC_CACHE_NONE; + } + + persistent_static = zend_get_attribute_str(attributes, ZEND_STRL(ZEND_OPCACHE_STATIC_CACHE_PERSISTENT_ATTRIBUTE)) != NULL; + volatile_static = zend_get_attribute_str(attributes, ZEND_STRL(ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_ATTRIBUTE)); + if (persistent_static && volatile_static != NULL) { + return ZEND_OPCACHE_STATIC_CACHE_CONFLICT; + } + + if (persistent_static) { + return ZEND_OPCACHE_STATIC_CACHE_PERSISTENT; + } + + if (volatile_static != NULL) { + return zend_opcache_static_cache_kind_from_volatile_static_attribute(volatile_static, scope, ttl); + } + + return ZEND_OPCACHE_STATIC_CACHE_NONE; +} + +static bool zend_opcache_static_cache_handle_attribute_conflict(zend_opcache_static_cache_kind kind) +{ + if (kind == ZEND_OPCACHE_STATIC_CACHE_CONFLICT) { + zend_error(E_ERROR, "OPcache\\PersistentStatic and OPcache\\VolatileStatic cannot be combined on the same target"); + + return false; + } + + return true; +} + +static zend_opcache_static_cache_kind zend_opcache_static_cache_class_blob_kind_and_ttl( + const zend_class_entry *ce, + zend_long *ttl) +{ + if (ce == NULL) { + if (ttl != NULL) { + *ttl = 0; + } + + return ZEND_OPCACHE_STATIC_CACHE_NONE; + } + + return zend_opcache_static_cache_kind_from_attributes(ce->attributes, (zend_class_entry *) ce, ttl); +} + +static zend_opcache_static_cache_kind zend_opcache_static_cache_class_blob_kind(const zend_class_entry *ce) +{ + return zend_opcache_static_cache_class_blob_kind_and_ttl(ce, NULL); +} + +static bool zend_opcache_static_cache_class_blob_enabled(const zend_class_entry *ce) +{ + zend_opcache_static_cache_kind kind = zend_opcache_static_cache_class_blob_kind(ce); + + return zend_opcache_static_cache_handle_attribute_conflict(kind) && + kind != ZEND_OPCACHE_STATIC_CACHE_NONE + ; +} + +static zend_opcache_static_cache_context *zend_opcache_static_cache_context_for_kind(zend_opcache_static_cache_kind kind) +{ + return kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC || kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING + ? &zend_opcache_static_cache_volatile_context_state + : &zend_opcache_static_cache_persistent_context_state + ; +} + +static const char *zend_opcache_static_cache_key_prefix_for_kind(zend_opcache_static_cache_kind kind) +{ + return kind == ZEND_OPCACHE_STATIC_CACHE_PERSISTENT ? "persistent_static" : "volatile_static"; +} + +static bool zend_opcache_static_cache_kind_tracks_reachable_arrays( + zend_opcache_static_cache_kind kind) +{ + return kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING || kind == ZEND_OPCACHE_STATIC_CACHE_PERSISTENT; +} + +static bool zend_opcache_static_cache_kind_tracks_reachable_objects( + zend_opcache_static_cache_kind kind) +{ + return kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING; +} + +static bool zend_opcache_static_cache_kind_tracks_reachable_mutations( + zend_opcache_static_cache_kind kind) +{ + return zend_opcache_static_cache_kind_tracks_reachable_arrays(kind) || + zend_opcache_static_cache_kind_tracks_reachable_objects(kind) + ; +} + +static bool zend_opcache_static_cache_kind_publishes_immediately( + zend_opcache_static_cache_kind kind) +{ + return kind == ZEND_OPCACHE_STATIC_CACHE_PERSISTENT || kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC; +} + +static bool zend_opcache_static_cache_kind_defers_reachable_mutation_publish( + zend_opcache_static_cache_kind kind) +{ + return kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING; +} + +static bool zend_opcache_static_cache_applies_to_static_property(zend_class_entry *ce, zend_property_info *property_info) +{ + zend_opcache_static_cache_kind kind; + + if (!(property_info->flags & ZEND_ACC_STATIC) || property_info->ce != ce) { + return false; + } + + if (zend_opcache_static_cache_class_blob_enabled(ce)) { + return false; + } + + kind = zend_opcache_static_cache_kind_from_attributes(property_info->attributes, ce, NULL); + + return zend_opcache_static_cache_handle_attribute_conflict(kind) && + kind != ZEND_OPCACHE_STATIC_CACHE_NONE + ; +} + +static zend_opcache_static_cache_kind zend_opcache_static_cache_static_property_kind_and_ttl( + zend_class_entry *ce, + zend_property_info *property_info, + zend_long *ttl) +{ + zend_opcache_static_cache_kind kind; + + if (ttl != NULL) { + *ttl = 0; + } + + if (!zend_opcache_static_cache_applies_to_static_property(ce, property_info)) { + return ZEND_OPCACHE_STATIC_CACHE_NONE; + } + + kind = zend_opcache_static_cache_kind_from_attributes(property_info->attributes, ce, ttl); + + return kind == ZEND_OPCACHE_STATIC_CACHE_CONFLICT + ? ZEND_OPCACHE_STATIC_CACHE_NONE + : kind + ; +} + +static zend_opcache_static_cache_kind zend_opcache_static_cache_static_property_kind(zend_class_entry *ce, zend_property_info *property_info) +{ + return zend_opcache_static_cache_static_property_kind_and_ttl(ce, property_info, NULL); +} + +static bool zend_opcache_static_cache_class_blob_applies_to_property(zend_class_entry *ce, zend_property_info *property_info) +{ + return (property_info->flags & ZEND_ACC_STATIC) != 0 && + property_info->ce == ce && + zend_opcache_static_cache_class_blob_enabled(ce) + ; +} + +static bool zend_opcache_static_cache_applies_to_function_static(const zend_op_array *op_array) +{ + zend_opcache_static_cache_kind kind; + + if (op_array->static_variables == NULL || op_array->function_name == NULL || op_array->scope == NULL) { + return false; + } + + if (op_array->scope != NULL && zend_opcache_static_cache_class_blob_enabled(op_array->scope)) { + return false; + } + + kind = zend_opcache_static_cache_kind_from_attributes(op_array->attributes, op_array->scope, NULL); + + return zend_opcache_static_cache_handle_attribute_conflict(kind) && + kind != ZEND_OPCACHE_STATIC_CACHE_NONE + ; +} + +static zend_opcache_static_cache_kind zend_opcache_static_cache_op_array_kind_and_ttl( + const zend_op_array *op_array, + zend_long *ttl) +{ + zend_opcache_static_cache_kind kind; + + if (ttl != NULL) { + *ttl = 0; + } + + if (!zend_opcache_static_cache_applies_to_function_static(op_array)) { + return ZEND_OPCACHE_STATIC_CACHE_NONE; + } + + kind = zend_opcache_static_cache_kind_from_attributes(op_array->attributes, op_array->scope, ttl); + + return kind == ZEND_OPCACHE_STATIC_CACHE_CONFLICT + ? ZEND_OPCACHE_STATIC_CACHE_NONE + : kind + ; +} + +static zend_opcache_static_cache_kind zend_opcache_static_cache_op_array_kind(const zend_op_array *op_array) +{ + return zend_opcache_static_cache_op_array_kind_and_ttl(op_array, NULL); +} + +static bool zend_opcache_static_cache_class_blob_applies_to_function_static(const zend_op_array *op_array) +{ + return op_array->static_variables != NULL && + op_array->function_name != NULL && + op_array->scope != NULL && + zend_opcache_static_cache_class_blob_enabled(op_array->scope) + ; +} + +static void zend_opcache_static_cache_track_attribute_class(zend_class_entry *ce) +{ + if (!zend_opcache_static_cache_attribute_classes_initialized || ce->name == NULL) { + return; + } + + zend_hash_index_update_ptr(&zend_opcache_static_cache_attribute_classes, (zend_ulong)(uintptr_t) ce, ce); +} + +static zend_string *zend_opcache_static_cache_static_property_key(zend_class_entry *ce, zend_string *property_name, zend_opcache_static_cache_kind kind) +{ + if (ce->name == NULL) { + return NULL; + } + + return zend_strpprintf(0, "%s:%s::$%s", zend_opcache_static_cache_key_prefix_for_kind(kind), ZSTR_VAL(ce->name), ZSTR_VAL(property_name)); +} + +static zend_string *zend_opcache_static_cache_class_blob_key(const zend_class_entry *ce, zend_opcache_static_cache_kind kind) +{ + if (ce == NULL || ce->name == NULL) { + return NULL; + } + + return zend_strpprintf(0, "%s_class:%s", zend_opcache_static_cache_key_prefix_for_kind(kind), ZSTR_VAL(ce->name)); +} + +static zend_string *zend_opcache_static_cache_function_variable_key(const zend_op_array *op_array, zend_string *var_name, zend_opcache_static_cache_kind kind) +{ + if (op_array->function_name == NULL) { + return NULL; + } + + if (op_array->scope != NULL && op_array->scope->name != NULL) { + return zend_strpprintf( + 0, + "%s:%s::%s()::$%s", + zend_opcache_static_cache_key_prefix_for_kind(kind), + ZSTR_VAL(op_array->scope->name), + ZSTR_VAL(op_array->function_name), + ZSTR_VAL(var_name) + ); + } + + if (op_array->filename == NULL) { + return NULL; + } + + return zend_strpprintf( + 0, + "%s:%s:%u:%s()::$%s", + zend_opcache_static_cache_key_prefix_for_kind(kind), + ZSTR_VAL(op_array->filename), + op_array->line_start, + ZSTR_VAL(op_array->function_name), + ZSTR_VAL(var_name) + ); +} + +static zend_string *zend_opcache_static_cache_class_blob_method_key(const zend_op_array *op_array) +{ + if (op_array == NULL || op_array->scope == NULL || op_array->scope->name == NULL || op_array->function_name == NULL) { + return NULL; + } + + return zend_strpprintf( + 0, + "%s::%s()", + ZSTR_VAL(op_array->scope->name), + ZSTR_VAL(op_array->function_name) + ); +} + +static zend_opcache_static_cache_class_blob_handle *zend_opcache_static_cache_find_class_blob_handle(zend_class_entry *ce) +{ + if (!zend_opcache_static_cache_class_blob_handles_initialized || ce == NULL) { + return NULL; + } + + return zend_hash_index_find_ptr(&zend_opcache_static_cache_class_blob_handles, (zend_ulong) (uintptr_t) ce); +} + +static zend_reference *zend_opcache_static_cache_create_reference(zval *value) +{ + zend_reference *ref = emalloc(sizeof(zend_reference)); + + GC_SET_REFCOUNT(ref, 1); + GC_TYPE_INFO(ref) = GC_REFERENCE; + ref->sources.ptr = NULL; + ZVAL_COPY_VALUE(&ref->val, value); + + return ref; +} + +static zend_reference *zend_opcache_static_cache_ensure_member_reference(zval *member) +{ + zval copy; + + if (Z_ISREF_P(member)) { + return Z_REF_P(member); + } + + ZVAL_COPY_VALUE(©, member); + ZVAL_REF(member, zend_opcache_static_cache_create_reference(©)); + + return Z_REF_P(member); +} + +static void zend_opcache_static_cache_bind_slot_to_member(zval *slot, zval *member) +{ + zend_reference *ref; + + if (slot == NULL || member == NULL) { + return; + } + + ref = zend_opcache_static_cache_ensure_member_reference(member); + ZVAL_DEINDIRECT(slot); + if (Z_ISREF_P(slot) && Z_REF_P(slot) == ref) { + return; + } + + zval_ptr_dtor(slot); + GC_ADDREF(ref); + ZVAL_REF(slot, ref); +} + +static bool zend_opcache_static_cache_tracks_reachable_mutations( + zend_opcache_static_cache_static_slot_handle *handle) +{ + return handle != NULL && zend_opcache_static_cache_kind_tracks_reachable_mutations(handle->kind); +} + +static bool zend_opcache_static_cache_tracks_reachable_arrays( + zend_opcache_static_cache_static_slot_handle *handle) +{ + return handle != NULL && zend_opcache_static_cache_kind_tracks_reachable_arrays(handle->kind); +} + +static bool zend_opcache_static_cache_tracks_reachable_objects( + zend_opcache_static_cache_static_slot_handle *handle) +{ + return handle != NULL && zend_opcache_static_cache_kind_tracks_reachable_objects(handle->kind); +} + +static void zend_opcache_static_cache_reset_pending_mutation(void) +{ + memset(&zend_opcache_static_cache_pending_mutation_state, 0, sizeof(zend_opcache_static_cache_pending_mutation_state)); +} + +static bool zend_opcache_static_cache_has_mutable_immediate_root( + zend_opcache_static_cache_static_slot_handle *handle) +{ + return handle != NULL && + zend_opcache_static_cache_kind_publishes_immediately(handle->kind) && + (handle->original_root_type == IS_ARRAY || handle->original_root_type == IS_OBJECT) + ; +} + +static void zend_opcache_static_cache_tracked_dependency_dtor(zval *zv) +{ + zend_opcache_static_cache_tracked_dependency *dependency = Z_PTR_P(zv); + + if (dependency == NULL) { + return; + } + + if (dependency->handles != NULL) { + zend_hash_destroy(dependency->handles); + efree(dependency->handles); + } + + efree(dependency); +} + +static void zend_opcache_static_cache_tracked_dependency_add_handle( + zend_opcache_static_cache_tracked_dependency *dependency, + zend_opcache_static_cache_static_slot_handle *handle) +{ + if (dependency == NULL || handle == NULL) { + return; + } + + if (dependency->handles != NULL) { + zend_hash_index_update_ptr(dependency->handles, (zend_ulong) (uintptr_t) handle, handle); + + return; + } + + if (dependency->single_handle == NULL || dependency->single_handle == handle) { + dependency->single_handle = handle; + + return; + } + + dependency->handles = emalloc(sizeof(HashTable)); + zend_hash_init(dependency->handles, 2, NULL, NULL, 0); + zend_hash_index_update_ptr(dependency->handles, (zend_ulong) (uintptr_t) dependency->single_handle, dependency->single_handle); + zend_hash_index_update_ptr(dependency->handles, (zend_ulong) (uintptr_t) handle, handle); + dependency->single_handle = NULL; +} + +static void zend_opcache_static_cache_tracked_dependency_remove_handle( + zend_opcache_static_cache_tracked_dependency *dependency, + zend_opcache_static_cache_static_slot_handle *handle) +{ + if (dependency == NULL || handle == NULL) { + return; + } + + if (dependency->handles != NULL) { + zend_hash_index_del(dependency->handles, (zend_ulong) (uintptr_t) handle); + + return; + } + + if (dependency->single_handle == handle) { + dependency->single_handle = NULL; + } +} + +static void zend_opcache_static_cache_untrack_dependency_handles( + HashTable *dependencies, + zend_opcache_static_cache_static_slot_handle *handle) +{ + zend_opcache_static_cache_tracked_dependency *dependency; + + if (dependencies == NULL || handle == NULL) { + return; + } + + ZEND_HASH_FOREACH_PTR(dependencies, dependency) { + zend_opcache_static_cache_tracked_dependency_remove_handle(dependency, handle); + } ZEND_HASH_FOREACH_END(); +} + +static void zend_opcache_static_cache_untrack_dependencies( + zend_opcache_static_cache_static_slot_handle *handle) +{ + if (handle == NULL) { + return; + } + + zend_opcache_static_cache_untrack_dependency_handles(zend_opcache_static_cache_tracked_arrays, handle); + zend_opcache_static_cache_untrack_dependency_handles(zend_opcache_static_cache_tracked_objects, handle); + zend_opcache_static_cache_last_array_ht = NULL; + zend_opcache_static_cache_last_array_dependency = NULL; + zend_opcache_static_cache_last_object_obj = NULL; + zend_opcache_static_cache_last_object_dependency = NULL; +} + +static zend_opcache_static_cache_tracked_dependency *zend_opcache_static_cache_track_dependency( + HashTable *dependencies, + zend_ulong key, + zend_opcache_static_cache_static_slot_handle *handle) +{ + zend_opcache_static_cache_tracked_dependency *dependency; + + if (dependencies == NULL || handle == NULL) { + return NULL; + } + + dependency = zend_hash_index_find_ptr(dependencies, key); + if (dependency == NULL) { + dependency = emalloc(sizeof(*dependency)); + dependency->single_handle = NULL; + dependency->handles = NULL; + zend_hash_index_update_ptr(dependencies, key, dependency); + } + + zend_opcache_static_cache_tracked_dependency_add_handle(dependency, handle); + + return dependency; +} + +static void zend_opcache_static_cache_prepare_memo_dependency_add_entry( + zend_opcache_static_cache_prepare_memo_dependency *dependency, + zend_opcache_static_cache_prepare_memo_entry *entry) +{ + if (dependency == NULL || entry == NULL) { + return; + } + + if (dependency->entries != NULL) { + zend_hash_index_update_ptr(dependency->entries, (zend_ulong) (uintptr_t) entry, entry); + + return; + } + + if (dependency->single_entry == NULL || dependency->single_entry == entry) { + dependency->single_entry = entry; + + return; + } + + dependency->entries = emalloc(sizeof(HashTable)); + zend_hash_init(dependency->entries, 2, NULL, NULL, 0); + zend_hash_index_update_ptr(dependency->entries, (zend_ulong) (uintptr_t) dependency->single_entry, dependency->single_entry); + zend_hash_index_update_ptr(dependency->entries, (zend_ulong) (uintptr_t) entry, entry); + dependency->single_entry = NULL; +} + +static zend_opcache_static_cache_prepare_memo_dependency *zend_opcache_static_cache_prepare_memo_track_dependency( + HashTable *dependencies, + zend_ulong key, + zend_opcache_static_cache_prepare_memo_entry *entry) +{ + zend_opcache_static_cache_prepare_memo_dependency *dependency; + + if (dependencies == NULL || entry == NULL) { + return NULL; + } + + dependency = zend_hash_index_find_ptr(dependencies, key); + if (dependency == NULL) { + dependency = emalloc(sizeof(*dependency)); + dependency->single_entry = NULL; + dependency->entries = NULL; + zend_hash_index_update_ptr(dependencies, key, dependency); + } + + zend_opcache_static_cache_prepare_memo_dependency_add_entry(dependency, entry); + + return dependency; +} + +static void zend_opcache_static_cache_prepare_memo_mark_dependency_dirty( + zend_opcache_static_cache_prepare_memo_dependency *dependency) +{ + zend_opcache_static_cache_prepare_memo_entry *entry; + + if (dependency == NULL) { + return; + } + + if (dependency->entries != NULL) { + ZEND_HASH_FOREACH_PTR(dependency->entries, entry) { + if (entry != NULL) { + entry->dirty = true; + } + } ZEND_HASH_FOREACH_END(); + + return; + } + + if (dependency->single_entry != NULL) { + dependency->single_entry->dirty = true; + } +} + +static HashTable *zend_opcache_static_cache_prepare_memo_root_table_for_value(zval *value) +{ + if (value == NULL) { + return NULL; + } + + ZVAL_DEREF(value); + if (Z_TYPE_P(value) == IS_ARRAY) { + return zend_opcache_static_cache_prepare_memo_array_roots; + } + + if (Z_TYPE_P(value) == IS_OBJECT) { + return zend_opcache_static_cache_prepare_memo_object_roots; + } + + return NULL; +} + +static zend_ulong zend_opcache_static_cache_prepare_memo_root_key(zval *value) +{ + ZVAL_DEREF(value); + + return Z_TYPE_P(value) == IS_ARRAY + ? (zend_ulong) (uintptr_t) Z_ARRVAL_P(value) + : (zend_ulong) (uintptr_t) Z_OBJ_P(value) + ; +} + +static zend_opcache_static_cache_prepare_memo_entry *zend_opcache_static_cache_prepare_memo_find_root_entry(zval *value) +{ + zend_opcache_static_cache_prepare_memo_entry *entry; + zend_ulong key; + HashTable *roots; + + roots = zend_opcache_static_cache_prepare_memo_root_table_for_value(value); + if (roots == NULL) { + return NULL; + } + + key = zend_opcache_static_cache_prepare_memo_root_key(value); + entry = zend_hash_index_find_ptr(roots, key); + if (entry != NULL && entry->dirty) { + zend_hash_index_del(roots, key); + return NULL; + } + + return entry; +} + +static zend_opcache_static_cache_tracked_dependency *zend_opcache_static_cache_find_tracked_array(HashTable *ht) +{ + zend_opcache_static_cache_tracked_dependency *dependency; + + if (ht == NULL || zend_opcache_static_cache_tracked_arrays == NULL) { + return NULL; + } + + /* Single-slot fast path: same pointer as the previous lookup. The cache + * stores both positive and negative hits and is invalidated whenever the + * tracked-pointer map changes. */ + if (ht == zend_opcache_static_cache_last_array_ht) { + return zend_opcache_static_cache_last_array_dependency; + } + + dependency = zend_hash_index_find_ptr(zend_opcache_static_cache_tracked_arrays, (zend_ulong) (uintptr_t) ht); + zend_opcache_static_cache_last_array_ht = ht; + zend_opcache_static_cache_last_array_dependency = dependency; + + return dependency; +} + +static zend_opcache_static_cache_tracked_dependency *zend_opcache_static_cache_find_tracked_object(zend_object *obj) +{ + zend_opcache_static_cache_tracked_dependency *dependency; + + if (obj == NULL || zend_opcache_static_cache_tracked_objects == NULL) { + return NULL; + } + + /* Single-slot fast path: same pointer as the previous lookup. The cache + * stores both positive and negative hits and is invalidated whenever the + * tracked-pointer map changes. */ + if (obj == zend_opcache_static_cache_last_object_obj) { + return zend_opcache_static_cache_last_object_dependency; + } + + dependency = zend_hash_index_find_ptr(zend_opcache_static_cache_tracked_objects, (zend_ulong) (uintptr_t) obj); + zend_opcache_static_cache_last_object_obj = obj; + zend_opcache_static_cache_last_object_dependency = dependency; + + return dependency; +} + +static bool zend_opcache_static_cache_slot_references(zval *slot, zend_reference *ref) +{ + if (slot == NULL || ref == NULL) { + return false; + } + + ZVAL_DEINDIRECT(slot); + + return Z_ISREF_P(slot) && Z_REF_P(slot) == ref; +} + +static void zend_opcache_static_cache_track_reference( + zval *slot, + zend_opcache_static_cache_static_slot_handle *handle) +{ + zend_reference *ref; + + if (zend_opcache_static_cache_tracked_references == NULL || slot == NULL || handle == NULL) { + return; + } + + ZVAL_DEINDIRECT(slot); + if (!Z_ISREF_P(slot)) { + return; + } + + ref = Z_REF_P(slot); + zend_hash_index_update_ptr(zend_opcache_static_cache_tracked_references, (zend_ulong) (uintptr_t) ref, handle); +} + +static zend_opcache_static_cache_static_slot_handle *zend_opcache_static_cache_find_tracked_reference(zend_reference *ref) +{ + zend_opcache_static_cache_static_slot_handle *handle; + zend_ulong key; + + if (ref == NULL || zend_opcache_static_cache_tracked_roots == NULL) { + return NULL; + } + + key = (zend_ulong) (uintptr_t) ref; + if (zend_opcache_static_cache_tracked_references != NULL) { + handle = zend_hash_index_find_ptr(zend_opcache_static_cache_tracked_references, key); + if (handle != NULL) { + if (zend_opcache_static_cache_slot_references(handle->slot, ref)) { + return handle; + } + + zend_hash_index_del(zend_opcache_static_cache_tracked_references, key); + } + } + + ZEND_HASH_FOREACH_PTR(zend_opcache_static_cache_tracked_roots, handle) { + if (handle != NULL && zend_opcache_static_cache_slot_references(handle->slot, ref)) { + if (zend_opcache_static_cache_tracked_references != NULL) { + zend_hash_index_update_ptr(zend_opcache_static_cache_tracked_references, key, handle); + } + + return handle; + } + } ZEND_HASH_FOREACH_END(); + + return NULL; +} + +static void zend_opcache_static_cache_function_static_entry_dtor(zval *zv) +{ + zend_opcache_static_cache_function_static_entry *entry = Z_PTR_P(zv); + + if (entry == NULL) { + return; + } + + zend_string_release(entry->cache_key); + + if (entry->method_key != NULL) { + zend_string_release(entry->method_key); + } + + if (entry->var_name != NULL) { + zend_string_release(entry->var_name); + } + + efree(entry); +} + +static void zend_opcache_static_cache_class_blob_handle_dtor(zval *zv) +{ + zend_opcache_static_cache_class_blob_handle *handle = Z_PTR_P(zv); + + if (handle == NULL) { + return; + } + + zend_string_release(handle->cache_key); + if (Z_TYPE(handle->root_state) != IS_UNDEF) { + zval_ptr_dtor(&handle->root_state); + } + + efree(handle); +} + +static zend_opcache_static_cache_class_blob_handle *zend_opcache_static_cache_ensure_class_blob_handle(zend_class_entry *ce) +{ + zend_opcache_static_cache_class_blob_handle *handle; + zend_opcache_static_cache_kind kind; + zend_string *cache_key; + zend_long ttl = 0; + + if (!zend_opcache_static_cache_class_blob_handles_initialized || ce == NULL) { + return NULL; + } + + handle = zend_opcache_static_cache_find_class_blob_handle(ce); + if (handle != NULL) { + return handle; + } + + kind = zend_opcache_static_cache_class_blob_kind_and_ttl(ce, &ttl); + if (!zend_opcache_static_cache_handle_attribute_conflict(kind) || + kind == ZEND_OPCACHE_STATIC_CACHE_NONE + ) { + return NULL; + } + + cache_key = zend_opcache_static_cache_class_blob_key(ce, kind); + if (cache_key == NULL) { + return NULL; + } + + handle = ecalloc(1, sizeof(*handle)); + handle->ce = ce; + handle->cache_key = cache_key; + handle->context = zend_opcache_static_cache_context_for_kind(kind); + handle->kind = kind; + handle->ttl = ttl; + + ZVAL_UNDEF(&handle->root_state); + zend_hash_index_update_ptr(&zend_opcache_static_cache_class_blob_handles, (zend_ulong) (uintptr_t) ce, handle); + + return handle; +} + +static void zend_opcache_static_cache_track_function_static_slot( + zval *slot, + zend_string *cache_key, + zend_opcache_static_cache_kind kind, + zend_long ttl) +{ + zend_opcache_static_cache_function_static_entry *entry; + zend_ulong key; + + if (!zend_opcache_static_cache_function_statics_initialized || slot == NULL || cache_key == NULL) { + return; + } + + key = (zend_ulong) (uintptr_t) slot; + if (zend_hash_index_exists(&zend_opcache_static_cache_function_statics, key)) { + return; + } + + entry = emalloc(sizeof(*entry)); + entry->slot = slot; + entry->cache_key = zend_string_copy(cache_key); + entry->context = zend_opcache_static_cache_context_for_kind(kind); + entry->kind = kind; + entry->ttl = ttl; + entry->class_blob_handle = NULL; + entry->method_key = NULL; + entry->var_name = NULL; + + zend_hash_index_update_ptr(&zend_opcache_static_cache_function_statics, key, entry); +} + +static void zend_opcache_static_cache_track_class_blob_function_static_slot( + zval *slot, + zend_opcache_static_cache_class_blob_handle *class_blob_handle, + zend_string *method_key, + zend_string *var_name) +{ + zend_opcache_static_cache_function_static_entry *entry; + zend_ulong key; + + if (!zend_opcache_static_cache_function_statics_initialized || + slot == NULL || + class_blob_handle == NULL || + method_key == NULL || + var_name == NULL + ) { + return; + } + + key = (zend_ulong) (uintptr_t) slot; + if (zend_hash_index_exists(&zend_opcache_static_cache_function_statics, key)) { + return; + } + + entry = emalloc(sizeof(*entry)); + entry->slot = slot; + entry->cache_key = zend_string_copy(class_blob_handle->cache_key); + entry->context = class_blob_handle->context; + entry->kind = class_blob_handle->kind; + entry->ttl = class_blob_handle->ttl; + entry->class_blob_handle = class_blob_handle; + entry->method_key = zend_string_copy(method_key); + entry->var_name = zend_string_copy(var_name); + + zend_hash_index_update_ptr(&zend_opcache_static_cache_function_statics, key, entry); +} + +static void zend_opcache_static_cache_class_blob_ensure_root_arrays( + zend_opcache_static_cache_class_blob_handle *handle) +{ + zval root_props, root_methods; + HashTable *root_ht; + + if (handle == NULL) { + return; + } + + if (Z_TYPE(handle->root_state) != IS_ARRAY) { + if (Z_TYPE(handle->root_state) != IS_UNDEF) { + zval_ptr_dtor(&handle->root_state); + } + + array_init(&handle->root_state); + } + + root_ht = Z_ARRVAL(handle->root_state); + if (zend_hash_str_find(root_ht, ZEND_STRL("properties")) == NULL) { + array_init(&root_props); + zend_hash_str_update(root_ht, ZEND_STRL("properties"), &root_props); + } + + if (zend_hash_str_find(root_ht, ZEND_STRL("methods")) == NULL) { + array_init(&root_methods); + zend_hash_str_update(root_ht, ZEND_STRL("methods"), &root_methods); + } +} + +static zval *zend_opcache_static_cache_class_blob_ensure_section( + zend_opcache_static_cache_class_blob_handle *handle, + const char *name, + size_t name_len) +{ + zval *section, new_section; + + zend_opcache_static_cache_class_blob_ensure_root_arrays(handle); + section = zend_hash_str_find(Z_ARRVAL(handle->root_state), name, name_len); + if (section != NULL && Z_TYPE_P(section) == IS_ARRAY) { + return section; + } + + array_init(&new_section); + + return zend_hash_str_update(Z_ARRVAL(handle->root_state), name, name_len, &new_section); +} + +static HashTable *zend_opcache_static_cache_class_blob_properties_ht( + zend_opcache_static_cache_class_blob_handle *handle) +{ + zval *properties; + + zend_opcache_static_cache_class_blob_ensure_root_arrays(handle); + properties = zend_hash_str_find(Z_ARRVAL(handle->root_state), ZEND_STRL("properties")); + + return (properties != NULL && Z_TYPE_P(properties) == IS_ARRAY) + ? Z_ARRVAL_P(properties) + : NULL + ; +} + +static HashTable *zend_opcache_static_cache_class_blob_methods_ht( + zend_opcache_static_cache_class_blob_handle *handle +) +{ + zval *methods; + + zend_opcache_static_cache_class_blob_ensure_root_arrays(handle); + methods = zend_hash_str_find(Z_ARRVAL(handle->root_state), ZEND_STRL("methods")); + + return (methods != NULL && Z_TYPE_P(methods) == IS_ARRAY) + ? Z_ARRVAL_P(methods) + : NULL + ; +} + +static bool zend_opcache_static_cache_detach_class_blob_root_state( + zend_opcache_static_cache_class_blob_handle *handle +) +{ + zend_string *method_key; + zval new_root, properties_copy, methods_copy, method_copy, *properties, *methods, *method_state; + + if (handle == NULL || Z_TYPE(handle->root_state) != IS_ARRAY) { + return false; + } + + array_init(&new_root); + properties = zend_hash_str_find(Z_ARRVAL(handle->root_state), ZEND_STRL("properties")); + if (properties != NULL && Z_TYPE_P(properties) == IS_ARRAY) { + ZVAL_ARR(&properties_copy, zend_array_dup(Z_ARRVAL_P(properties))); + } else { + array_init(&properties_copy); + } + + zend_hash_str_update(Z_ARRVAL(new_root), ZEND_STRL("properties"), &properties_copy); + + methods = zend_hash_str_find(Z_ARRVAL(handle->root_state), ZEND_STRL("methods")); + array_init_size(&methods_copy, + (methods != NULL && Z_TYPE_P(methods) == IS_ARRAY) + ? zend_hash_num_elements(Z_ARRVAL_P(methods)) + : 0 + ); + if (methods != NULL && Z_TYPE_P(methods) == IS_ARRAY) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(methods), method_key, method_state) { + if (method_key == NULL) { + continue; + } + + if (Z_TYPE_P(method_state) == IS_ARRAY) { + ZVAL_ARR(&method_copy, zend_array_dup(Z_ARRVAL_P(method_state))); + } else { + ZVAL_COPY(&method_copy, method_state); + } + + zend_hash_update(Z_ARRVAL(methods_copy), method_key, &method_copy); + } ZEND_HASH_FOREACH_END(); + } + + zend_hash_str_update(Z_ARRVAL(new_root), ZEND_STRL("methods"), &methods_copy); + + zval_ptr_dtor(&handle->root_state); + ZVAL_COPY_VALUE(&handle->root_state, &new_root); + + return true; +} + +static bool zend_opcache_static_cache_build_default_class_blob_state( + zend_opcache_static_cache_class_blob_handle *handle) +{ + zend_string *property_name; + zend_property_info *property_info; + zval copy, *source; + HashTable *properties_ht; + + if (handle == NULL) { + return false; + } + + if (Z_TYPE(handle->root_state) != IS_UNDEF) { + zval_ptr_dtor(&handle->root_state); + } + + array_init(&handle->root_state); + zend_opcache_static_cache_class_blob_ensure_root_arrays(handle); + properties_ht = zend_opcache_static_cache_class_blob_properties_ht(handle); + if (properties_ht == NULL) { + return false; + } + + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&handle->ce->properties_info, property_name, property_info) { + if (property_name == NULL || !zend_opcache_static_cache_class_blob_applies_to_property(handle->ce, property_info)) { + continue; + } + + if (CE_STATIC_MEMBERS(handle->ce) != NULL) { + source = CE_STATIC_MEMBERS(handle->ce) + property_info->offset; + ZVAL_DEINDIRECT(source); + } else { + source = &handle->ce->default_static_members_table[property_info->offset]; + } + + ZVAL_COPY_DEREF(©, source); + + zend_hash_update(properties_ht, property_name, ©); + } ZEND_HASH_FOREACH_END(); + + handle->initialized = true; + handle->dirty = true; + handle->request_deferred_publish = false; + + return true; +} + +static bool zend_opcache_static_cache_refresh_class_blob_handle_locked( + zend_opcache_static_cache_class_blob_handle *handle) +{ + zval cached_value; + + if (handle == NULL) { + return false; + } + + if (zend_opcache_static_cache_fetch_locked(handle->cache_key, &cached_value, false, NULL, false)) { + if (Z_TYPE(cached_value) == IS_ARRAY) { + if (Z_TYPE(handle->root_state) != IS_UNDEF) { + zval_ptr_dtor(&handle->root_state); + } + + ZVAL_COPY_VALUE(&handle->root_state, &cached_value); + if (!zend_opcache_static_cache_detach_class_blob_root_state(handle)) { + zval_ptr_dtor(&handle->root_state); + ZVAL_UNDEF(&handle->root_state); + if (!handle->initialized) { + return zend_opcache_static_cache_build_default_class_blob_state(handle); + } + + return false; + } + + handle->initialized = true; + handle->dirty = false; + handle->request_deferred_publish = zend_opcache_static_cache_kind_defers_reachable_mutation_publish(handle->kind); + zend_opcache_static_cache_class_blob_ensure_root_arrays(handle); + + return true; + } + + zval_ptr_dtor(&cached_value); + } + + if (!handle->initialized) { + return zend_opcache_static_cache_build_default_class_blob_state(handle); + } + + zend_opcache_static_cache_class_blob_ensure_root_arrays(handle); + + return true; +} + +static bool zend_opcache_static_cache_class_blob_build_snapshot( + zend_opcache_static_cache_class_blob_handle *handle, + zval *snapshot) +{ + zend_string *key, *var_name; + zval properties_snapshot, methods_snapshot, method_snapshot, copy, *value, *var_value; + HashTable *properties_ht, *methods_ht, *method_ht; + + if (handle == NULL) { + return false; + } + + array_init(snapshot); + array_init(&properties_snapshot); + array_init(&methods_snapshot); + + zend_hash_str_update(Z_ARRVAL_P(snapshot), ZEND_STRL("properties"), &properties_snapshot); + zend_hash_str_update(Z_ARRVAL_P(snapshot), ZEND_STRL("methods"), &methods_snapshot); + + properties_ht = zend_opcache_static_cache_class_blob_properties_ht(handle); + if (properties_ht != NULL) { + ZEND_HASH_FOREACH_STR_KEY_VAL(properties_ht, key, value) { + if (key == NULL) { + continue; + } + + ZVAL_COPY_DEREF(©, value); + + zend_hash_update(Z_ARRVAL(properties_snapshot), key, ©); + } ZEND_HASH_FOREACH_END(); + } + + methods_ht = zend_opcache_static_cache_class_blob_methods_ht(handle); + if (methods_ht != NULL) { + ZEND_HASH_FOREACH_STR_KEY_VAL(methods_ht, key, value) { + if (key == NULL || Z_TYPE_P(value) != IS_ARRAY) { + continue; + } + + array_init(&method_snapshot); + method_ht = Z_ARRVAL_P(value); + ZEND_HASH_FOREACH_STR_KEY_VAL(method_ht, var_name, var_value) { + if (var_name == NULL) { + continue; + } + + ZVAL_COPY_DEREF(©, var_value); + zend_hash_update(Z_ARRVAL(method_snapshot), var_name, ©); + } ZEND_HASH_FOREACH_END(); + + zend_hash_update(Z_ARRVAL(methods_snapshot), key, &method_snapshot); + } ZEND_HASH_FOREACH_END(); + } + + return true; +} + +static bool zend_opcache_static_cache_publish_class_blob_handle_locked( + zend_opcache_static_cache_class_blob_handle *handle) +{ + zval snapshot; + bool result; + + if (handle == NULL) { + return false; + } + + ZVAL_UNDEF(&snapshot); + if (!handle->dirty) { + return true; + } + + result = zend_opcache_static_cache_class_blob_build_snapshot(handle, &snapshot) && + zend_opcache_static_cache_store_locked(handle->cache_key, &snapshot, handle->ttl, false) + ; + + zval_ptr_dtor(&snapshot); + if (result) { + handle->dirty = false; + } + + return result; +} + +static void zend_opcache_static_cache_tracked_root_dtor(zval *zv) +{ + zend_opcache_static_cache_static_slot_handle *handle = Z_PTR_P(zv); + + if (handle == NULL) { + return; + } + + zend_string_release(handle->cache_key); + if (handle->has_original_value) { + zval_ptr_dtor(&handle->original_value); + } + + efree(handle); +} + +static bool zend_opcache_static_cache_value_changed(zval *current, zval *original) +{ + zval current_deref, original_deref; + bool changed; + + ZVAL_COPY_DEREF(¤t_deref, current); + ZVAL_COPY_DEREF(&original_deref, original); + + if (Z_TYPE(current_deref) != Z_TYPE(original_deref)) { + zval_ptr_dtor(¤t_deref); + zval_ptr_dtor(&original_deref); + + return true; + } + + switch (Z_TYPE(current_deref)) { + case IS_UNDEF: + case IS_NULL: + case IS_FALSE: + case IS_TRUE: + zval_ptr_dtor(¤t_deref); + zval_ptr_dtor(&original_deref); + + return false; + case IS_LONG: + changed = Z_LVAL(current_deref) != Z_LVAL(original_deref); + zval_ptr_dtor(¤t_deref); + zval_ptr_dtor(&original_deref); + + return changed; + case IS_DOUBLE: + changed = Z_DVAL(current_deref) != Z_DVAL(original_deref); + zval_ptr_dtor(¤t_deref); + zval_ptr_dtor(&original_deref); + + return changed; + case IS_STRING: + changed = !zend_string_equal_content(Z_STR(current_deref), Z_STR(original_deref)); + zval_ptr_dtor(¤t_deref); + zval_ptr_dtor(&original_deref); + + return changed; + case IS_ARRAY: + changed = Z_ARR(current_deref) != Z_ARR(original_deref); + zval_ptr_dtor(¤t_deref); + zval_ptr_dtor(&original_deref); + + return changed; + case IS_OBJECT: + changed = Z_OBJ(current_deref) != Z_OBJ(original_deref); + zval_ptr_dtor(¤t_deref); + zval_ptr_dtor(&original_deref); + + return changed; + default: + zval_ptr_dtor(¤t_deref); + zval_ptr_dtor(&original_deref); + + return true; + } +} + +static void zend_opcache_static_cache_set_tracked_root_original(zend_opcache_static_cache_static_slot_handle *handle) +{ + zval *value; + + if (handle == NULL || handle->slot == NULL) { + return; + } + + if (handle->has_original_value) { + zval_ptr_dtor(&handle->original_value); + } else { + handle->has_original_value = true; + } + + ZVAL_UNDEF(&handle->original_value); + handle->original_root = NULL; + handle->original_root_type = IS_UNDEF; + handle->snapshot_root_published = false; + + value = handle->slot; + ZVAL_DEINDIRECT(value); + ZVAL_DEREF(value); + if (zend_opcache_static_cache_kind_publishes_immediately(handle->kind) && + (Z_TYPE_P(value) == IS_ARRAY || Z_TYPE_P(value) == IS_OBJECT) + ) { + /* Keep only the root identity for immediate mutable values. Taking a + * strong zval copy would force array COW and make nested writes look like + * root reassignments during request shutdown. */ + handle->original_root = Z_COUNTED_P(value); + handle->original_root_type = Z_TYPE_P(value); + + return; + } + + ZVAL_COPY_DEREF(&handle->original_value, value); +} + +static zend_opcache_static_cache_static_slot_handle *zend_opcache_static_cache_ensure_tracked_root( + zval *slot, + zend_string *cache_key, + zend_opcache_static_cache_context *context, + zend_opcache_static_cache_kind kind, + zend_long ttl, + zend_opcache_static_cache_class_blob_handle *class_blob_handle) +{ + zend_opcache_static_cache_static_slot_handle *handle; + zend_ulong key; + + if (slot == NULL || cache_key == NULL || zend_opcache_static_cache_tracked_roots == NULL) { + return NULL; + } + + key = (zend_ulong) (uintptr_t) slot; + handle = zend_hash_index_find_ptr(zend_opcache_static_cache_tracked_roots, key); + if (handle != NULL) { + handle->class_blob_handle = class_blob_handle; + if (class_blob_handle != NULL) { + handle->context = class_blob_handle->context; + handle->kind = class_blob_handle->kind; + handle->ttl = class_blob_handle->ttl; + handle->request_deferred_publish = class_blob_handle->request_deferred_publish; + } else { + handle->context = context; + handle->kind = kind; + handle->ttl = ttl; + } + zend_opcache_static_cache_track_reference(slot, handle); + + return handle; + } + + handle = emalloc(sizeof(*handle)); + handle->slot = slot; + handle->cache_key = zend_string_copy(cache_key); + handle->context = class_blob_handle != NULL ? class_blob_handle->context : context; + handle->kind = class_blob_handle != NULL ? class_blob_handle->kind : kind; + handle->ttl = class_blob_handle != NULL ? class_blob_handle->ttl : ttl; + handle->class_blob_handle = class_blob_handle; + ZVAL_UNDEF(&handle->original_value); + handle->original_root = NULL; + handle->original_root_type = IS_UNDEF; + handle->has_original_value = false; + handle->snapshot_root_published = false; + handle->request_deferred_publish = class_blob_handle != NULL && class_blob_handle->request_deferred_publish; + handle->mutation_dirty = false; + zend_opcache_static_cache_set_tracked_root_original(handle); + zend_hash_index_update_ptr(zend_opcache_static_cache_tracked_roots, key, handle); + zend_opcache_static_cache_track_reference(slot, handle); + + return handle; +} + +static bool zend_opcache_static_cache_static_slot_value_changed(zend_opcache_static_cache_static_slot_handle *handle) +{ + zval *value; + + if (handle == NULL || !handle->has_original_value || handle->slot == NULL) { + return true; + } + + value = handle->slot; + ZVAL_DEINDIRECT(value); + ZVAL_DEREF(value); + if (handle->original_root_type == IS_ARRAY || handle->original_root_type == IS_OBJECT) { + bool root_changed = Z_TYPE_P(value) != handle->original_root_type || + Z_COUNTED_P(value) != handle->original_root; + + if (handle->snapshot_root_published && zend_opcache_static_cache_has_mutable_immediate_root(handle)) { + return handle->mutation_dirty; + } + + return handle->mutation_dirty || root_changed; + } + + return handle->mutation_dirty || zend_opcache_static_cache_value_changed(value, &handle->original_value); +} + +static bool zend_opcache_static_cache_pending_hash_is_root_array( + zend_opcache_static_cache_static_slot_handle *handle, + HashTable *ht) +{ + zval *value; + + if (handle == NULL || handle->slot == NULL || ht == NULL) { + return false; + } + + value = handle->slot; + ZVAL_DEINDIRECT(value); + ZVAL_DEREF(value); + + return Z_TYPE_P(value) == IS_ARRAY && Z_ARRVAL_P(value) == ht; +} + +static bool zend_opcache_static_cache_value_contains_reachable_array( + zval *value, + HashTable *needle, + HashTable *seen_arrays) +{ + zend_ulong key; + zval *child; + HashTable *ht; + + if (value == NULL || needle == NULL) { + return false; + } + + ZVAL_DEREF(value); + if (Z_TYPE_P(value) != IS_ARRAY) { + return false; + } + + ht = Z_ARRVAL_P(value); + if (ht == needle) { + return true; + } + + key = (zend_ulong) (uintptr_t) ht; + if (zend_hash_index_exists(seen_arrays, key)) { + return false; + } + + zend_hash_index_add_empty_element(seen_arrays, key); + ZEND_HASH_FOREACH_VAL(ht, child) { + if (zend_opcache_static_cache_value_contains_reachable_array(child, needle, seen_arrays)) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + +static bool zend_opcache_static_cache_pending_hash_is_current_reachable_array( + zend_opcache_static_cache_static_slot_handle *handle, + HashTable *ht) +{ + zval *value; + HashTable seen_arrays; + bool reachable; + + if (handle == NULL || + handle->slot == NULL || + ht == NULL || + !zend_opcache_static_cache_tracks_reachable_arrays(handle) + ) { + return false; + } + + value = handle->slot; + ZVAL_DEINDIRECT(value); + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + reachable = zend_opcache_static_cache_value_contains_reachable_array(value, ht, &seen_arrays); + zend_hash_destroy(&seen_arrays); + + return reachable; +} + +static bool zend_opcache_static_cache_tracked_root_changed(zval *slot) +{ + zend_opcache_static_cache_static_slot_handle *handle; + + if (slot == NULL || zend_opcache_static_cache_tracked_roots == NULL) { + return true; + } + + handle = zend_hash_index_find_ptr(zend_opcache_static_cache_tracked_roots, (zend_ulong) (uintptr_t) slot); + + return zend_opcache_static_cache_static_slot_value_changed(handle); +} + +static bool zend_opcache_static_cache_class_blob_changed(zend_opcache_static_cache_class_blob_handle *class_blob_handle) +{ + zend_opcache_static_cache_static_slot_handle *handle; + + if (class_blob_handle == NULL) { + return false; + } + + if (class_blob_handle->dirty) { + return true; + } + + if (zend_opcache_static_cache_tracked_roots == NULL) { + return false; + } + + ZEND_HASH_FOREACH_PTR(zend_opcache_static_cache_tracked_roots, handle) { + if (handle != NULL && + handle->class_blob_handle == class_blob_handle && + zend_opcache_static_cache_static_slot_value_changed(handle) + ) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + +static bool zend_opcache_static_cache_register_captured_values(zend_opcache_static_cache_static_slot_handle *handle) +{ + zend_object *obj; + zend_ulong key; + HashTable *ht; + + if (!zend_opcache_static_cache_capture_available || handle == NULL) { + return false; + } + + if (zend_opcache_static_cache_capture_arrays != NULL && + zend_opcache_static_cache_tracked_arrays != NULL && + zend_opcache_static_cache_tracks_reachable_arrays(handle) + ) { + ZEND_HASH_FOREACH_NUM_KEY(zend_opcache_static_cache_capture_arrays, key) { + ht = (HashTable *) (uintptr_t) key; + zend_opcache_static_cache_track_dependency(zend_opcache_static_cache_tracked_arrays, key, handle); + zend_opcache_static_cache_has_tracked_arrays = true; + if (zend_opcache_static_cache_last_array_ht == ht) { + zend_opcache_static_cache_last_array_ht = NULL; + zend_opcache_static_cache_last_array_dependency = NULL; + } + } ZEND_HASH_FOREACH_END(); + } + + if (zend_opcache_static_cache_capture_objects != NULL && + zend_opcache_static_cache_tracked_objects != NULL && + zend_opcache_static_cache_tracks_reachable_objects(handle) + ) { + ZEND_HASH_FOREACH_NUM_KEY(zend_opcache_static_cache_capture_objects, key) { + obj = (zend_object *) (uintptr_t) key; + zend_opcache_static_cache_track_dependency(zend_opcache_static_cache_tracked_objects, key, handle); + zend_opcache_static_cache_has_tracked_objects = true; + if (zend_opcache_static_cache_last_object_obj == obj) { + zend_opcache_static_cache_last_object_obj = NULL; + zend_opcache_static_cache_last_object_dependency = NULL; + } + } ZEND_HASH_FOREACH_END(); + } + + zend_opcache_static_cache_update_mutation_hook_state(); + zend_opcache_static_cache_capture_clear(); + + return true; +} + +static void zend_opcache_static_cache_track_value_recursive( + zval *value, + zend_opcache_static_cache_static_slot_handle *handle, + HashTable *seen_arrays, + HashTable *seen_objects +) +{ + zend_object *obj; + zend_ulong key; + zend_string *property_name; + zval *property_value, *source_value; + HashTable *ht, *properties; + + if (value == NULL || handle == NULL) { + return; + } + + ZVAL_DEREF(value); + switch (Z_TYPE_P(value)) { + case IS_ARRAY: + ht = Z_ARRVAL_P(value); + key = (zend_ulong) (uintptr_t) ht; + + if (zend_hash_index_exists(seen_arrays, key)) { + return; + } + + if (!zend_opcache_static_cache_tracks_reachable_arrays(handle)) { + return; + } + + zend_hash_index_add_empty_element(seen_arrays, key); + if (zend_opcache_static_cache_tracked_arrays != NULL) { + zend_opcache_static_cache_track_dependency(zend_opcache_static_cache_tracked_arrays, key, handle); + zend_opcache_static_cache_has_tracked_arrays = true; + if (zend_opcache_static_cache_last_array_ht == ht) { + zend_opcache_static_cache_last_array_ht = NULL; + zend_opcache_static_cache_last_array_dependency = NULL; + } + } + + zend_opcache_static_cache_update_mutation_hook_state(); + + ZEND_HASH_FOREACH_VAL(ht, property_value) { + zend_opcache_static_cache_track_value_recursive(property_value, handle, seen_arrays, seen_objects); + } ZEND_HASH_FOREACH_END(); + + return; + case IS_OBJECT: + obj = Z_OBJ_P(value); + + if (!zend_opcache_static_cache_tracks_reachable_objects(handle)) { + /* Immediate attributes snapshot assigned objects at the root. Later object + * mutations require an explicit reassignment to publish. */ + return; + } + + key = (zend_ulong) (uintptr_t) obj; + + if (zend_hash_index_exists(seen_objects, key)) { + return; + } + + zend_hash_index_add_empty_element(seen_objects, key); + + if (zend_opcache_static_cache_tracked_objects != NULL) { + zend_opcache_static_cache_track_dependency(zend_opcache_static_cache_tracked_objects, key, handle); + zend_opcache_static_cache_has_tracked_objects = true; + if (zend_opcache_static_cache_last_object_obj == obj) { + zend_opcache_static_cache_last_object_obj = NULL; + zend_opcache_static_cache_last_object_dependency = NULL; + } + } + + zend_opcache_static_cache_update_mutation_hook_state(); + + properties = zend_get_properties_for(value, ZEND_PROP_PURPOSE_SERIALIZE); + if (properties == NULL) { + return; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(properties, property_name, property_value) { + if (property_name == NULL) { + continue; + } + + source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + zend_opcache_static_cache_track_value_recursive(source_value, handle, seen_arrays, seen_objects); + } ZEND_HASH_FOREACH_END(); + + zend_release_properties(properties); + + return; + default: + return; + } +} + +static bool zend_opcache_static_cache_prepare_memo_can_track_object(zend_class_entry *ce) +{ + return ce != NULL && + ce->type == ZEND_USER_CLASS && + zend_opcache_serializer_find_safe_direct_cache_base(ce) == NULL && + ce->create_object == NULL && + ce->__serialize == NULL && + ce->__unserialize == NULL && + !zend_hash_str_exists(&ce->function_table, ZEND_STRL("__sleep")) && + !zend_hash_str_exists(&ce->function_table, ZEND_STRL("__wakeup")); +} + +static bool zend_opcache_static_cache_prepare_memo_can_track_value_recursive( + zval *value, + HashTable *seen_arrays, + HashTable *seen_objects) +{ + zend_object *obj; + zend_ulong key; + zend_string *property_name; + zval *child, *property_value, *source_value; + HashTable *ht, *properties; + + if (value == NULL) { + return false; + } + + ZVAL_DEREF(value); + switch (Z_TYPE_P(value)) { + case IS_UNDEF: + case IS_NULL: + case IS_FALSE: + case IS_TRUE: + case IS_LONG: + case IS_DOUBLE: + case IS_STRING: + return true; + case IS_ARRAY: + ht = Z_ARRVAL_P(value); + key = (zend_ulong) (uintptr_t) ht; + if (zend_hash_index_exists(seen_arrays, key)) { + return true; + } + + zend_hash_index_add_empty_element(seen_arrays, key); + ZEND_HASH_FOREACH_VAL(ht, child) { + if (!zend_opcache_static_cache_prepare_memo_can_track_value_recursive(child, seen_arrays, seen_objects)) { + return false; + } + } ZEND_HASH_FOREACH_END(); + + return true; + case IS_OBJECT: + obj = Z_OBJ_P(value); + if (!zend_opcache_static_cache_prepare_memo_can_track_object(obj->ce)) { + return false; + } + + key = (zend_ulong) (uintptr_t) obj; + if (zend_hash_index_exists(seen_objects, key)) { + return true; + } + + zend_hash_index_add_empty_element(seen_objects, key); + properties = zend_get_properties_for(value, ZEND_PROP_PURPOSE_SERIALIZE); + if (properties == NULL) { + return true; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(properties, property_name, property_value) { + if (property_name == NULL) { + continue; + } + + source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + if (!zend_opcache_static_cache_prepare_memo_can_track_value_recursive(source_value, seen_arrays, seen_objects)) { + zend_release_properties(properties); + return false; + } + } ZEND_HASH_FOREACH_END(); + + zend_release_properties(properties); + + return true; + default: + return false; + } +} + +static void zend_opcache_static_cache_prepare_memo_track_value_recursive( + zval *value, + zend_opcache_static_cache_prepare_memo_entry *entry, + HashTable *seen_arrays, + HashTable *seen_objects) +{ + zend_object *obj; + zend_ulong key; + zend_string *property_name; + zval *child, *property_value, *source_value; + HashTable *ht, *properties; + + if (value == NULL || entry == NULL) { + return; + } + + ZVAL_DEREF(value); + switch (Z_TYPE_P(value)) { + case IS_ARRAY: + ht = Z_ARRVAL_P(value); + key = (zend_ulong) (uintptr_t) ht; + if (zend_hash_index_exists(seen_arrays, key)) { + return; + } + + zend_hash_index_add_empty_element(seen_arrays, key); + zend_opcache_static_cache_prepare_memo_track_dependency(zend_opcache_static_cache_prepare_memo_arrays, key, entry); + zend_opcache_static_cache_has_prepare_memo_arrays = true; + ZEND_HASH_FOREACH_VAL(ht, child) { + zend_opcache_static_cache_prepare_memo_track_value_recursive(child, entry, seen_arrays, seen_objects); + } ZEND_HASH_FOREACH_END(); + + return; + case IS_OBJECT: + obj = Z_OBJ_P(value); + key = (zend_ulong) (uintptr_t) obj; + if (zend_hash_index_exists(seen_objects, key)) { + return; + } + + zend_hash_index_add_empty_element(seen_objects, key); + zend_opcache_static_cache_prepare_memo_track_dependency(zend_opcache_static_cache_prepare_memo_objects, key, entry); + zend_opcache_static_cache_has_prepare_memo_objects = true; + properties = zend_get_properties_for(value, ZEND_PROP_PURPOSE_SERIALIZE); + if (properties == NULL) { + return; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(properties, property_name, property_value) { + if (property_name == NULL) { + continue; + } + + source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + zend_opcache_static_cache_prepare_memo_track_value_recursive(source_value, entry, seen_arrays, seen_objects); + } ZEND_HASH_FOREACH_END(); + + zend_release_properties(properties); + + return; + default: + return; + } +} +static void zend_opcache_static_cache_register_tracked_value_ex( + zval *slot, + zend_string *cache_key, + zend_opcache_static_cache_context *context, + zend_opcache_static_cache_kind kind, + zend_long ttl, + zend_opcache_static_cache_class_blob_handle *class_blob_handle) +{ + zend_opcache_static_cache_static_slot_handle *handle; + HashTable seen_arrays, seen_objects; + + if (slot == NULL || cache_key == NULL) { + return; + } + + handle = zend_opcache_static_cache_ensure_tracked_root(slot, cache_key, context, kind, ttl, class_blob_handle); + if (handle == NULL) { + return; + } + + zend_opcache_static_cache_untrack_dependencies(handle); + + if (zend_opcache_static_cache_register_captured_values(handle)) { + return; + } + + if (!zend_opcache_static_cache_tracks_reachable_mutations(handle)) { + return; + } + + handle->request_deferred_publish = zend_opcache_static_cache_kind_defers_reachable_mutation_publish(handle->kind); + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&seen_objects, 8, NULL, NULL, 0); + + zend_opcache_static_cache_track_value_recursive(slot, handle, &seen_arrays, &seen_objects); + zend_hash_destroy(&seen_objects); + zend_hash_destroy(&seen_arrays); +} + +static void zend_opcache_static_cache_register_tracked_value(zval *slot, zend_string *cache_key, zend_opcache_static_cache_kind kind, zend_long ttl) +{ + zend_opcache_static_cache_register_tracked_value_ex(slot, cache_key, zend_opcache_static_cache_context_for_kind(kind), kind, ttl, NULL); +} + +static void zend_opcache_static_cache_refresh_class_blob_root_originals( + zend_opcache_static_cache_class_blob_handle *class_blob_handle) +{ + zend_opcache_static_cache_static_slot_handle *handle; + + if (class_blob_handle == NULL || zend_opcache_static_cache_tracked_roots == NULL) { + return; + } + + class_blob_handle->request_deferred_publish = false; + + ZEND_HASH_FOREACH_PTR(zend_opcache_static_cache_tracked_roots, handle) { + if (handle == NULL || handle->class_blob_handle != class_blob_handle) { + continue; + } + + zend_opcache_static_cache_set_tracked_root_original(handle); + handle->mutation_dirty = false; + handle->request_deferred_publish = false; + zend_opcache_static_cache_register_tracked_value_ex( + handle->slot, + handle->cache_key, + handle->context, + handle->kind, + handle->ttl, + class_blob_handle + ); + } ZEND_HASH_FOREACH_END(); +} + +static bool zend_opcache_static_cache_sync_class_blob_function_static_entry( + zend_opcache_static_cache_function_static_entry *entry) +{ + zval new_method_state, value_snapshot, *methods, *method_state, *value, *member; + + if (entry == NULL || + entry->slot == NULL || + entry->class_blob_handle == NULL || + entry->method_key == NULL || + entry->var_name == NULL + ) { + return false; + } + + methods = zend_opcache_static_cache_class_blob_ensure_section(entry->class_blob_handle, ZEND_STRL("methods")); + method_state = zend_hash_find(Z_ARRVAL_P(methods), entry->method_key); + if (method_state == NULL || Z_TYPE_P(method_state) != IS_ARRAY) { + array_init(&new_method_state); + method_state = zend_hash_update(Z_ARRVAL_P(methods), entry->method_key, &new_method_state); + } + + value = entry->slot; + ZVAL_DEINDIRECT(value); + if (Z_TYPE_P(value) == IS_UNDEF) { + return false; + } + + member = zend_hash_find(Z_ARRVAL_P(method_state), entry->var_name); + if (member != NULL && !zend_opcache_static_cache_value_changed(value, member)) { + return false; + } + + ZVAL_COPY_DEREF(&value_snapshot, value); + zend_hash_update(Z_ARRVAL_P(method_state), entry->var_name, &value_snapshot); + entry->class_blob_handle->dirty = true; + + return true; +} + +static void zend_opcache_static_cache_sync_class_blob_function_static_handle( + zend_opcache_static_cache_static_slot_handle *handle) +{ + zend_opcache_static_cache_function_static_entry *entry; + + if (handle == NULL || + handle->slot == NULL || + handle->class_blob_handle == NULL || + !zend_opcache_static_cache_function_statics_initialized + ) { + return; + } + + entry = zend_hash_index_find_ptr(&zend_opcache_static_cache_function_statics, (zend_ulong) (uintptr_t) handle->slot); + if (entry == NULL || entry->class_blob_handle != handle->class_blob_handle) { + return; + } + + zend_opcache_static_cache_sync_class_blob_function_static_entry(entry); +} + +static void zend_opcache_static_cache_publish_immediate_root(zend_opcache_static_cache_static_slot_handle *handle) +{ + zend_opcache_static_cache_context *previous_context; + zval value_snapshot, *slot; + bool published = false; + + if (handle == NULL || + !zend_opcache_static_cache_kind_publishes_immediately(handle->kind) || + !zend_opcache_static_cache_context_runtime(handle->context)->available || + !zend_opcache_static_cache_static_slot_value_changed(handle) + ) { + return; + } + + previous_context = zend_opcache_static_cache_activate_context(handle->context); + if (zend_opcache_static_cache_wlock()) { + if (zend_opcache_static_cache_header_init_locked()) { + if (handle->class_blob_handle != NULL) { + zend_opcache_static_cache_sync_class_blob_function_static_handle(handle); + handle->class_blob_handle->dirty = true; + published = zend_opcache_static_cache_publish_class_blob_handle_locked(handle->class_blob_handle); + } else { + slot = handle->slot; + ZVAL_DEINDIRECT(slot); + if (Z_TYPE_P(slot) == IS_UNDEF) { + published = zend_opcache_static_cache_delete_locked(handle->cache_key); + } else { + ZVAL_COPY_DEREF(&value_snapshot, slot); + published = zend_opcache_static_cache_store_locked(handle->cache_key, &value_snapshot, handle->ttl, false); + zval_ptr_dtor(&value_snapshot); + } + } + } + + zend_opcache_static_cache_unlock(); + } + + zend_opcache_static_cache_restore_context(previous_context); + + if (!published) { + return; + } + + if (handle->class_blob_handle != NULL) { + zend_opcache_static_cache_refresh_class_blob_root_originals(handle->class_blob_handle); + } else { + zend_opcache_static_cache_set_tracked_root_original(handle); + handle->snapshot_root_published = zend_opcache_static_cache_has_mutable_immediate_root(handle); + handle->mutation_dirty = false; + handle->request_deferred_publish = false; + zend_opcache_static_cache_register_tracked_value(handle->slot, handle->cache_key, handle->kind, handle->ttl); + } +} + +static void zend_opcache_static_cache_publish_class_blob_fast(zend_opcache_static_cache_class_blob_handle *class_blob_handle) +{ + zend_opcache_static_cache_context *previous_context; + bool published = false; + + if (class_blob_handle == NULL || + !zend_opcache_static_cache_kind_publishes_immediately(class_blob_handle->kind) || + !class_blob_handle->initialized || + !zend_opcache_static_cache_context_runtime(class_blob_handle->context)->available || + !zend_opcache_static_cache_class_blob_changed(class_blob_handle) + ) { + return; + } + + previous_context = zend_opcache_static_cache_activate_context(class_blob_handle->context); + if (zend_opcache_static_cache_wlock()) { + if (zend_opcache_static_cache_header_init_locked()) { + /* Immediate class blobs publish root assignments immediately. PersistentStatic + * array graphs are re-registered after publication so later array writes + * also fail or succeed at the write site. */ + class_blob_handle->dirty = true; + published = zend_opcache_static_cache_publish_class_blob_handle_locked(class_blob_handle); + } + zend_opcache_static_cache_unlock(); + } + + zend_opcache_static_cache_restore_context(previous_context); + + if (published) { + zend_opcache_static_cache_refresh_class_blob_root_originals(class_blob_handle); + } +} + +static bool zend_opcache_static_cache_install_class_blob_property_refs( + zend_opcache_static_cache_class_blob_handle *handle) +{ + zend_string *property_name; + zend_property_info *property_info; + zval snapshot, *properties, *slot, *member; + + if (handle == NULL || handle->ce == NULL || CE_STATIC_MEMBERS(handle->ce) == NULL) { + return false; + } + + properties = zend_opcache_static_cache_class_blob_ensure_section(handle, ZEND_STRL("properties")); + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&handle->ce->properties_info, property_name, property_info) { + if (property_name == NULL || !zend_opcache_static_cache_class_blob_applies_to_property(handle->ce, property_info)) { + continue; + } + + slot = CE_STATIC_MEMBERS(handle->ce) + property_info->offset; + ZVAL_DEINDIRECT(slot); + member = zend_hash_find(Z_ARRVAL_P(properties), property_name); + if (member == NULL) { + ZVAL_COPY_DEREF(&snapshot, slot); + member = zend_hash_update(Z_ARRVAL_P(properties), property_name, &snapshot); + handle->dirty = true; + } + + zend_opcache_static_cache_bind_slot_to_member(slot, member); + zend_opcache_static_cache_register_tracked_value_ex(slot, handle->cache_key, handle->context, handle->kind, handle->ttl, handle); + } ZEND_HASH_FOREACH_END(); + + return true; +} + +static bool zend_opcache_static_cache_bind_class_blob_method_refs( + zend_opcache_static_cache_class_blob_handle *handle, + zend_op_array *op_array, + HashTable *static_variables) +{ + zend_string *method_key, *var_name; + zval new_method_state, snapshot, *methods, *method_state, *slot, *member; + + if (handle == NULL || op_array == NULL || static_variables == NULL) { + return false; + } + + methods = zend_opcache_static_cache_class_blob_ensure_section(handle, ZEND_STRL("methods")); + method_key = zend_opcache_static_cache_class_blob_method_key(op_array); + if (method_key == NULL) { + return false; + } + + method_state = zend_hash_find(Z_ARRVAL_P(methods), method_key); + if (method_state == NULL || Z_TYPE_P(method_state) != IS_ARRAY) { + array_init(&new_method_state); + method_state = zend_hash_update(Z_ARRVAL_P(methods), method_key, &new_method_state); + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(static_variables, var_name, slot) { + if (var_name == NULL) { + continue; + } + + zend_opcache_static_cache_track_class_blob_function_static_slot(slot, handle, method_key, var_name); + + member = zend_hash_find(Z_ARRVAL_P(method_state), var_name); + if (member == NULL) { + if (Z_TYPE_P(slot) == IS_NULL) { + handle->dirty = true; + zend_opcache_static_cache_register_tracked_value_ex(slot, handle->cache_key, handle->context, handle->kind, handle->ttl, handle); + + continue; + } else { + ZVAL_COPY_DEREF(&snapshot, slot); + member = zend_hash_update(Z_ARRVAL_P(method_state), var_name, &snapshot); + + handle->dirty = true; + } + } + + zend_opcache_static_cache_bind_slot_to_member(slot, member); + zend_opcache_static_cache_register_tracked_value_ex(slot, handle->cache_key, handle->context, handle->kind, handle->ttl, handle); + } ZEND_HASH_FOREACH_END(); + + zend_string_release(method_key); + + return true; +} + +static void zend_opcache_static_cache_init_tracking(void) +{ + zend_opcache_static_cache_has_tracked_arrays = false; + zend_opcache_static_cache_has_tracked_objects = false; + zend_opcache_static_cache_has_prepare_memo_arrays = false; + zend_opcache_static_cache_has_prepare_memo_objects = false; + zend_opcache_static_cache_capture_active = false; + zend_opcache_static_cache_capture_clear(); + zend_opcache_static_cache_last_array_ht = NULL; + zend_opcache_static_cache_last_array_dependency = NULL; + zend_opcache_static_cache_last_object_obj = NULL; + zend_opcache_static_cache_last_object_dependency = NULL; + zend_opcache_static_cache_update_mutation_hook_state(); + + if (zend_opcache_static_cache_tracked_roots == NULL) { + zend_opcache_static_cache_tracked_roots = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_tracked_roots, 8, NULL, zend_opcache_static_cache_tracked_root_dtor, 0); + } + + if (zend_opcache_static_cache_tracked_references == NULL) { + zend_opcache_static_cache_tracked_references = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_tracked_references, 8, NULL, NULL, 0); + } + + if (zend_opcache_static_cache_tracked_arrays == NULL) { + zend_opcache_static_cache_tracked_arrays = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_tracked_arrays, 8, NULL, zend_opcache_static_cache_tracked_dependency_dtor, 0); + } + + if (zend_opcache_static_cache_tracked_objects == NULL) { + zend_opcache_static_cache_tracked_objects = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_tracked_objects, 8, NULL, zend_opcache_static_cache_tracked_dependency_dtor, 0); + } + + zend_opcache_static_cache_reset_pending_mutation(); +} + +static void zend_opcache_static_cache_destroy_tracking(void) +{ + zend_opcache_static_cache_reset_pending_mutation(); + zend_opcache_static_cache_has_tracked_arrays = false; + zend_opcache_static_cache_has_tracked_objects = false; + zend_opcache_static_cache_has_prepare_memo_arrays = false; + zend_opcache_static_cache_has_prepare_memo_objects = false; + zend_opcache_static_cache_capture_active = false; + zend_opcache_static_cache_capture_available = false; + zend_opcache_static_cache_last_array_ht = NULL; + zend_opcache_static_cache_last_array_dependency = NULL; + zend_opcache_static_cache_last_object_obj = NULL; + zend_opcache_static_cache_last_object_dependency = NULL; + zend_opcache_static_cache_update_mutation_hook_state(); + + if (zend_opcache_static_cache_tracked_roots != NULL) { + zend_hash_destroy(zend_opcache_static_cache_tracked_roots); + efree(zend_opcache_static_cache_tracked_roots); + zend_opcache_static_cache_tracked_roots = NULL; + } + + if (zend_opcache_static_cache_tracked_references != NULL) { + zend_hash_destroy(zend_opcache_static_cache_tracked_references); + efree(zend_opcache_static_cache_tracked_references); + zend_opcache_static_cache_tracked_references = NULL; + } + + if (zend_opcache_static_cache_tracked_arrays != NULL) { + zend_hash_destroy(zend_opcache_static_cache_tracked_arrays); + efree(zend_opcache_static_cache_tracked_arrays); + zend_opcache_static_cache_tracked_arrays = NULL; + } + + if (zend_opcache_static_cache_tracked_objects != NULL) { + zend_hash_destroy(zend_opcache_static_cache_tracked_objects); + efree(zend_opcache_static_cache_tracked_objects); + zend_opcache_static_cache_tracked_objects = NULL; + } + + if (zend_opcache_static_cache_prepare_memo_entries != NULL) { + zend_hash_destroy(zend_opcache_static_cache_prepare_memo_entries); + efree(zend_opcache_static_cache_prepare_memo_entries); + zend_opcache_static_cache_prepare_memo_entries = NULL; + } + + if (zend_opcache_static_cache_prepare_memo_array_roots != NULL) { + zend_hash_destroy(zend_opcache_static_cache_prepare_memo_array_roots); + efree(zend_opcache_static_cache_prepare_memo_array_roots); + zend_opcache_static_cache_prepare_memo_array_roots = NULL; + } + + if (zend_opcache_static_cache_prepare_memo_object_roots != NULL) { + zend_hash_destroy(zend_opcache_static_cache_prepare_memo_object_roots); + efree(zend_opcache_static_cache_prepare_memo_object_roots); + zend_opcache_static_cache_prepare_memo_object_roots = NULL; + } + + if (zend_opcache_static_cache_prepare_memo_arrays != NULL) { + zend_hash_destroy(zend_opcache_static_cache_prepare_memo_arrays); + efree(zend_opcache_static_cache_prepare_memo_arrays); + zend_opcache_static_cache_prepare_memo_arrays = NULL; + } + + if (zend_opcache_static_cache_prepare_memo_objects != NULL) { + zend_hash_destroy(zend_opcache_static_cache_prepare_memo_objects); + efree(zend_opcache_static_cache_prepare_memo_objects); + zend_opcache_static_cache_prepare_memo_objects = NULL; + } + + if (zend_opcache_static_cache_capture_arrays != NULL) { + zend_hash_destroy(zend_opcache_static_cache_capture_arrays); + efree(zend_opcache_static_cache_capture_arrays); + zend_opcache_static_cache_capture_arrays = NULL; + } + + if (zend_opcache_static_cache_capture_objects != NULL) { + zend_hash_destroy(zend_opcache_static_cache_capture_objects); + efree(zend_opcache_static_cache_capture_objects); + zend_opcache_static_cache_capture_objects = NULL; + } +} + +static void zend_opcache_static_cache_mark_dirty_class_blob_handles(void) +{ + zend_opcache_static_cache_static_slot_handle *handle; + + if (zend_opcache_static_cache_tracked_roots == NULL) { + return; + } + + ZEND_HASH_FOREACH_PTR(zend_opcache_static_cache_tracked_roots, handle) { + if (handle == NULL || + handle->class_blob_handle == NULL || + !handle->has_original_value || + handle->slot == NULL + ) { + continue; + } + + if (zend_opcache_static_cache_static_slot_value_changed(handle)) { + handle->class_blob_handle->dirty = true; + } + } ZEND_HASH_FOREACH_END(); +} + +static void zend_opcache_static_cache_flush_pending(void) +{ + zend_opcache_static_cache_static_slot_handle *handle; + zend_opcache_static_cache_context *previous_context; + zval value_snapshot; + bool should_store, published = false; + + if (!zend_opcache_static_cache_pending_mutation_state.dirty || + zend_opcache_static_cache_pending_mutation_state.handle == NULL + ) { + zend_opcache_static_cache_reset_pending_mutation(); + + return; + } + + handle = zend_opcache_static_cache_pending_mutation_state.handle; + if (!zend_opcache_static_cache_context_runtime(handle->context)->available) { + zend_opcache_static_cache_reset_pending_mutation(); + + return; + } + + previous_context = zend_opcache_static_cache_activate_context(handle->context); + if (zend_opcache_static_cache_wlock()) { + if (zend_opcache_static_cache_header_init_locked()) { + if (handle->class_blob_handle != NULL) { + zend_opcache_static_cache_sync_class_blob_function_static_handle(handle); + handle->class_blob_handle->dirty = true; + published = zend_opcache_static_cache_publish_class_blob_handle_locked(handle->class_blob_handle); + } else { + ZVAL_COPY_DEREF(&value_snapshot, handle->slot); + should_store = Z_TYPE(value_snapshot) != IS_UNDEF; + if (should_store) { + published = zend_opcache_static_cache_store_locked(handle->cache_key, &value_snapshot, handle->ttl, false); + } else { + published = zend_opcache_static_cache_delete_locked(handle->cache_key); + } + + zval_ptr_dtor(&value_snapshot); + } + } + + zend_opcache_static_cache_unlock(); + } + + zend_opcache_static_cache_restore_context(previous_context); + + if (handle->class_blob_handle != NULL) { + if (published && zend_opcache_static_cache_kind_publishes_immediately(handle->kind)) { + zend_opcache_static_cache_refresh_class_blob_root_originals(handle->class_blob_handle); + } else { + zend_opcache_static_cache_register_tracked_value_ex(handle->slot, handle->cache_key, handle->context, handle->kind, handle->ttl, handle->class_blob_handle); + } + } else { + if (published && zend_opcache_static_cache_kind_publishes_immediately(handle->kind)) { + zend_opcache_static_cache_set_tracked_root_original(handle); + handle->snapshot_root_published = zend_opcache_static_cache_has_mutable_immediate_root(handle); + handle->mutation_dirty = false; + handle->request_deferred_publish = false; + } + zend_opcache_static_cache_register_tracked_value(handle->slot, handle->cache_key, handle->kind, handle->ttl); + } + + zend_opcache_static_cache_reset_pending_mutation(); +} + +static bool zend_opcache_static_cache_defer_mutation_publish(zend_opcache_static_cache_static_slot_handle *handle) +{ + if (handle == NULL || !handle->request_deferred_publish) { + return false; + } + + if (zend_opcache_static_cache_pending_mutation_state.handle != NULL && + zend_opcache_static_cache_pending_mutation_state.handle != handle + ) { + zend_opcache_static_cache_flush_pending(); + } + + handle->mutation_dirty = true; + + return true; +} + +static bool zend_opcache_static_cache_begin_mutation( + zend_opcache_static_cache_static_slot_handle *handle, + HashTable *ht) +{ + bool root_array; + + if (handle == NULL) { + return false; + } + + if (zend_opcache_static_cache_pending_mutation_state.handle != NULL && + zend_opcache_static_cache_pending_mutation_state.handle != handle + ) { + zend_opcache_static_cache_flush_pending(); + } + + root_array = zend_opcache_static_cache_pending_hash_is_root_array(handle, ht); + if (zend_opcache_static_cache_pending_mutation_state.handle == handle && + zend_opcache_static_cache_pending_mutation_state.dirty + ) { + zend_opcache_static_cache_pending_mutation_state.depth++; + zend_opcache_static_cache_pending_mutation_state.root_array &= root_array; + + return true; + } + + zend_opcache_static_cache_pending_mutation_state.handle = handle; + zend_opcache_static_cache_pending_mutation_state.depth = 1; + zend_opcache_static_cache_pending_mutation_state.dirty = true; + zend_opcache_static_cache_pending_mutation_state.root_array = root_array; + + return true; +} + +static bool zend_opcache_static_cache_defer_dependency_publish( + zend_opcache_static_cache_tracked_dependency *dependency) +{ + zend_opcache_static_cache_static_slot_handle *handle; + bool deferred = false; + + if (dependency == NULL) { + return false; + } + + if (dependency->handles == NULL) { + return zend_opcache_static_cache_defer_mutation_publish(dependency->single_handle); + } + + ZEND_HASH_FOREACH_PTR(dependency->handles, handle) { + if (zend_opcache_static_cache_defer_mutation_publish(handle)) { + deferred = true; + } + } ZEND_HASH_FOREACH_END(); + + return deferred; +} + +static zend_opcache_static_cache_static_slot_handle *zend_opcache_static_cache_tracked_dependency_first_handle( + zend_opcache_static_cache_tracked_dependency *dependency) +{ + zend_opcache_static_cache_static_slot_handle *handle; + + if (dependency == NULL) { + return NULL; + } + + if (dependency->handles == NULL) { + return dependency->single_handle; + } + + ZEND_HASH_FOREACH_PTR(dependency->handles, handle) { + return handle; + } ZEND_HASH_FOREACH_END(); + + return NULL; +} + +static void zend_opcache_static_cache_delete_method_keys_locked(zend_class_entry *ce) +{ + zend_opcache_static_cache_kind kind; + zend_function *function; + zend_op_array *op_array; + zend_string *var_name, *cache_key; + zval *slot; + + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, function) { + if (function == NULL || function->type != ZEND_USER_FUNCTION) { + continue; + } + + op_array = &function->op_array; + kind = zend_opcache_static_cache_op_array_kind(op_array); + if (kind == ZEND_OPCACHE_STATIC_CACHE_NONE || + zend_opcache_static_cache_context_for_kind(kind) != zend_opcache_static_cache_active_context() + ) { + continue; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(op_array->static_variables, var_name, slot) { + (void) slot; + + if (var_name == NULL) { + continue; + } + + cache_key = zend_opcache_static_cache_function_variable_key(op_array, var_name, kind); + if (cache_key == NULL) { + continue; + } + + zend_opcache_static_cache_delete_locked(cache_key); + zend_string_release(cache_key); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); +} + +static void zend_opcache_static_cache_delete_class_keys_locked(zend_class_entry *ce) +{ + zend_opcache_static_cache_kind kind; + zend_string *property_name, *cache_key; + zend_property_info *property_info; + + if (ce == NULL || ce->name == NULL) { + return; + } + + if (zend_opcache_static_cache_class_blob_enabled(ce)) { + kind = zend_opcache_static_cache_class_blob_kind(ce); + + if (zend_opcache_static_cache_context_for_kind(kind) != zend_opcache_static_cache_active_context()) { + return; + } + + cache_key = zend_opcache_static_cache_class_blob_key(ce, kind); + if (cache_key != NULL) { + zend_opcache_static_cache_delete_locked(cache_key); + zend_string_release(cache_key); + } + + return; + } + + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, property_name, property_info) { + if (property_name == NULL) { + continue; + } + + kind = zend_opcache_static_cache_static_property_kind(ce, property_info); + if (kind == ZEND_OPCACHE_STATIC_CACHE_NONE + || zend_opcache_static_cache_context_for_kind(kind) != zend_opcache_static_cache_active_context()) { + continue; + } + + cache_key = zend_opcache_static_cache_static_property_key(ce, property_name, kind); + if (cache_key == NULL) { + continue; + } + + zend_opcache_static_cache_delete_locked(cache_key); + zend_string_release(cache_key); + } ZEND_HASH_FOREACH_END(); + + zend_opcache_static_cache_delete_method_keys_locked(ce); +} + +/* Fast-path filter used by zend_opcache_static_cache_class_access(). The + * filter walks the property table only on first encounter; subsequent calls + * for the same class are answered from the ignored/classes memoization. */ +static bool zend_opcache_static_cache_class_access_filter( + zend_class_entry *ce, + zend_opcache_static_cache_class_blob_handle **blob_out) +{ + zend_opcache_static_cache_class_blob_handle *blob; + zend_string *property_name; + zend_property_info *property_info; + zend_ulong key; + + *blob_out = NULL; + if (UNEXPECTED(ce->name == NULL)) { + return false; + } + + blob = zend_opcache_static_cache_find_class_blob_handle(ce); + if (blob != NULL) { + *blob_out = blob; + + return true; + } + + key = (zend_ulong) (uintptr_t) ce; + if (zend_opcache_static_cache_ignored_classes_initialized && + zend_hash_index_exists(&zend_opcache_static_cache_ignored_classes, key) + ) { + return false; + } + + if (zend_opcache_static_cache_class_blob_enabled(ce)) { + return true; + } + + if (zend_opcache_static_cache_attribute_classes_initialized && + zend_hash_index_exists(&zend_opcache_static_cache_attribute_classes, key) + ) { + return false; + } + + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, property_name, property_info) { + if (property_name != NULL && zend_opcache_static_cache_applies_to_static_property(ce, property_info)) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + if (zend_opcache_static_cache_ignored_classes_initialized) { + zend_hash_index_add_empty_element(&zend_opcache_static_cache_ignored_classes, key); + } + + return false; +} + +static void zend_opcache_static_cache_publish_persistent_static_properties_fast(zend_class_entry *ce) +{ + zend_opcache_static_cache_context *context, *previous_context; + zend_opcache_static_cache_static_slot_handle *handle; + zend_opcache_static_cache_kind kind; + zend_string *property_name, *cache_key; + zend_property_info *property_info; + zend_ulong class_key; + zend_long ttl; + zval *slot; + bool published; + + if (!zend_opcache_static_cache_attribute_classes_initialized || ce->name == NULL) { + return; + } + + class_key = (zend_ulong) (uintptr_t) ce; + if (!zend_hash_index_exists(&zend_opcache_static_cache_attribute_classes, class_key)) { + return; + } + + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, property_name, property_info) { + if (property_name == NULL) { + continue; + } + + kind = zend_opcache_static_cache_static_property_kind_and_ttl(ce, property_info, &ttl); + if (!zend_opcache_static_cache_kind_publishes_immediately(kind)) { + continue; + } + + context = zend_opcache_static_cache_context_for_kind(kind); + if (!zend_opcache_static_cache_context_runtime(context)->available) { + continue; + } + + cache_key = zend_opcache_static_cache_static_property_key(ce, property_name, kind); + if (cache_key == NULL) { + continue; + } + + slot = CE_STATIC_MEMBERS(ce) + property_info->offset; + ZVAL_DEINDIRECT(slot); + handle = zend_opcache_static_cache_ensure_tracked_root(slot, cache_key, context, kind, ttl, NULL); + if (!zend_opcache_static_cache_static_slot_value_changed(handle)) { + zend_string_release(cache_key); + continue; + } + + published = false; + previous_context = zend_opcache_static_cache_activate_context(context); + if (zend_opcache_static_cache_wlock()) { + if (zend_opcache_static_cache_header_init_locked()) { + /* Immediate property assignments publish immediately so later + * mutations of the assigned graph do not change the snapshot. */ + if (Z_TYPE_P(slot) == IS_UNDEF) { + published = zend_opcache_static_cache_delete_locked(cache_key); + } else { + published = zend_opcache_static_cache_store_locked(cache_key, slot, ttl, false); + } + } + + zend_opcache_static_cache_unlock(); + } + + zend_opcache_static_cache_restore_context(previous_context); + + if (published) { + zend_opcache_static_cache_set_tracked_root_original(handle); + if (handle != NULL) { + handle->snapshot_root_published = zend_opcache_static_cache_has_mutable_immediate_root(handle); + handle->mutation_dirty = false; + handle->request_deferred_publish = false; + } + } + + zend_opcache_static_cache_register_tracked_value(slot, cache_key, kind, ttl); + zend_string_release(cache_key); + } ZEND_HASH_FOREACH_END(); +} + +static bool zend_opcache_static_cache_context_has_publish_work(zend_opcache_static_cache_context *context) +{ + zend_opcache_static_cache_class_blob_handle *class_blob_handle; + zend_opcache_static_cache_static_slot_handle *handle; + + if (zend_opcache_static_cache_class_blob_handles_initialized) { + ZEND_HASH_FOREACH_PTR(&zend_opcache_static_cache_class_blob_handles, class_blob_handle) { + if (class_blob_handle != NULL && + class_blob_handle->context == context && + zend_opcache_static_cache_class_blob_changed(class_blob_handle) + ) { + return true; + } + } ZEND_HASH_FOREACH_END(); + } + + if (zend_opcache_static_cache_tracked_roots != NULL) { + ZEND_HASH_FOREACH_PTR(zend_opcache_static_cache_tracked_roots, handle) { + if (handle != NULL && handle->context == context && zend_opcache_static_cache_static_slot_value_changed(handle)) { + return true; + } + } ZEND_HASH_FOREACH_END(); + } + + return false; +} + +static void zend_opcache_static_cache_publish_context(zend_opcache_static_cache_context *context) +{ + zend_opcache_static_cache_context *previous_context; + zend_opcache_static_cache_function_static_entry *entry; + zend_opcache_static_cache_class_blob_handle *class_blob_handle; + zend_opcache_static_cache_kind kind; + zend_class_entry *ce; + zend_string *property_name, *cache_key; + zend_property_info *property_info; + zend_long ttl; + zval value_snapshot, *value, *slot; + + if (!zend_opcache_static_cache_context_runtime(context)->available) { + return; + } + + if (zend_opcache_static_cache_publish_skipped(context)) { + return; + } + + if (!zend_opcache_static_cache_context_has_publish_work(context)) { + return; + } + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_wlock()) { + zend_opcache_static_cache_restore_context(previous_context); + + return; + } + + if (!zend_opcache_static_cache_header_init_locked()) { + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + + return; + } + + zend_opcache_static_cache_mark_dirty_class_blob_handles(); + + if (zend_opcache_static_cache_function_statics_initialized) { + ZEND_HASH_FOREACH_PTR(&zend_opcache_static_cache_function_statics, entry) { + if (entry == NULL || entry->slot == NULL || entry->class_blob_handle == NULL || entry->context != context) { + continue; + } + + zend_opcache_static_cache_sync_class_blob_function_static_entry(entry); + } ZEND_HASH_FOREACH_END(); + } + + if (zend_opcache_static_cache_class_blob_handles_initialized) { + ZEND_HASH_FOREACH_PTR(&zend_opcache_static_cache_class_blob_handles, class_blob_handle) { + if (class_blob_handle == NULL || class_blob_handle->context != context) { + continue; + } + + zend_opcache_static_cache_publish_class_blob_handle_locked(class_blob_handle); + } ZEND_HASH_FOREACH_END(); + } + + if (zend_opcache_static_cache_attribute_classes_initialized) { + ZEND_HASH_FOREACH_PTR(&zend_opcache_static_cache_attribute_classes, ce) { + if (ce == NULL || ce->name == NULL || CE_STATIC_MEMBERS(ce) == NULL) { + continue; + } + + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, property_name, property_info) { + if (property_name == NULL) { + continue; + } + + kind = zend_opcache_static_cache_static_property_kind_and_ttl(ce, property_info, &ttl); + if (kind == ZEND_OPCACHE_STATIC_CACHE_NONE || zend_opcache_static_cache_context_for_kind(kind) != context) { + continue; + } + + slot = CE_STATIC_MEMBERS(ce) + property_info->offset; + ZVAL_DEINDIRECT(slot); + if (!zend_opcache_static_cache_tracked_root_changed(slot)) { + continue; + } + + cache_key = zend_opcache_static_cache_static_property_key(ce, property_name, kind); + if (cache_key == NULL) { + continue; + } + + if (Z_TYPE_P(slot) == IS_UNDEF) { + zend_opcache_static_cache_delete_locked(cache_key); + } else { + zend_opcache_static_cache_store_locked(cache_key, slot, ttl, false); + } + + zend_string_release(cache_key); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + } + + if (zend_opcache_static_cache_function_statics_initialized) { + ZEND_HASH_FOREACH_PTR(&zend_opcache_static_cache_function_statics, entry) { + if (entry == NULL || entry->slot == NULL || entry->class_blob_handle != NULL || entry->context != context) { + continue; + } + + value = entry->slot; + if (!zend_opcache_static_cache_tracked_root_changed(value)) { + continue; + } + + ZVAL_DEINDIRECT(value); + ZVAL_COPY_DEREF(&value_snapshot, value); + if (Z_TYPE(value_snapshot) == IS_UNDEF) { + zend_opcache_static_cache_delete_locked(entry->cache_key); + } else { + zend_opcache_static_cache_store_locked(entry->cache_key, &value_snapshot, entry->ttl, false); + } + + zval_ptr_dtor(&value_snapshot); + } ZEND_HASH_FOREACH_END(); + } + + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); +} + +static void zend_opcache_static_cache_capture_decoded_reachable_value_ex( + zval *value, + HashTable *seen_arrays, + HashTable *seen_objects +) +{ + zend_object *obj; + zend_string *property_name; + zend_ulong key; + zval *child, *property_value, *source_value; + HashTable *ht, *properties; + + if (!zend_opcache_static_cache_capture_active || value == NULL) { + return; + } + + if (zend_opcache_static_cache_capture_handle != NULL && + zend_opcache_static_cache_capture_handle->kind == ZEND_OPCACHE_STATIC_CACHE_PERSISTENT + ) { + return; + } + + ZVAL_DEREF(value); + switch (Z_TYPE_P(value)) { + case IS_ARRAY: + ht = Z_ARRVAL_P(value); + key = (zend_ulong) (uintptr_t) ht; + + if (zend_hash_index_exists(seen_arrays, key)) { + return; + } + + zend_hash_index_add_empty_element(seen_arrays, key); + zend_opcache_static_cache_capture_decoded_value(value); + + ZEND_HASH_FOREACH_VAL(ht, child) { + zend_opcache_static_cache_capture_decoded_reachable_value_ex(child, seen_arrays, seen_objects); + } ZEND_HASH_FOREACH_END(); + + return; + case IS_OBJECT: + obj = Z_OBJ_P(value); + key = (zend_ulong) (uintptr_t) obj; + + if (zend_hash_index_exists(seen_objects, key)) { + return; + } + + zend_hash_index_add_empty_element(seen_objects, key); + zend_opcache_static_cache_capture_decoded_value(value); + if (zend_opcache_static_cache_capture_handle != NULL && + !zend_opcache_static_cache_tracks_reachable_objects(zend_opcache_static_cache_capture_handle) + ) { + return; + } + + properties = zend_get_properties_for(value, ZEND_PROP_PURPOSE_SERIALIZE); + if (properties == NULL) { + return; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(properties, property_name, property_value) { + if (property_name == NULL) { + continue; + } + source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + zend_opcache_static_cache_capture_decoded_reachable_value_ex(source_value, seen_arrays, seen_objects); + } ZEND_HASH_FOREACH_END(); + + zend_release_properties(properties); + + return; + default: + return; + } +} + +static bool zend_opcache_static_cache_hash_mutation(HashTable *ht, bool publish) +{ + zend_opcache_static_cache_tracked_dependency *dependency; + zend_opcache_static_cache_static_slot_handle *handle; + zend_opcache_static_cache_prepare_memo_dependency *memo_dependency; + + if (publish) { + if (zend_opcache_static_cache_pending_mutation_state.dirty && + zend_opcache_static_cache_pending_mutation_state.handle != NULL && + zend_opcache_static_cache_kind_publishes_immediately( + zend_opcache_static_cache_pending_mutation_state.handle->kind) && + zend_opcache_static_cache_tracks_reachable_arrays( + zend_opcache_static_cache_pending_mutation_state.handle) && + !zend_opcache_static_cache_pending_hash_is_current_reachable_array( + zend_opcache_static_cache_pending_mutation_state.handle, + ht) + ) { + zend_opcache_static_cache_reset_pending_mutation(); + } else { + zend_opcache_static_cache_flush_pending(); + } + + return false; + } + + if (ht != NULL && zend_opcache_static_cache_has_prepare_memo_arrays && zend_opcache_static_cache_prepare_memo_arrays != NULL) { + memo_dependency = zend_hash_index_find_ptr(zend_opcache_static_cache_prepare_memo_arrays, (zend_ulong) (uintptr_t) ht); + zend_opcache_static_cache_prepare_memo_mark_dependency_dirty(memo_dependency); + } + + if (ht == NULL || !zend_opcache_static_cache_has_tracked_arrays) { + return false; + } + + dependency = zend_opcache_static_cache_find_tracked_array(ht); + if (zend_opcache_static_cache_defer_dependency_publish(dependency)) { + return false; + } + + handle = zend_opcache_static_cache_tracked_dependency_first_handle(dependency); + + if (handle == NULL) { + if (zend_opcache_static_cache_pending_mutation_state.dirty && + zend_opcache_static_cache_pending_mutation_state.handle != NULL + ) { + zend_opcache_static_cache_reset_pending_mutation(); + } + + return false; + } + + return zend_opcache_static_cache_begin_mutation(handle, ht); +} + +static void zend_opcache_static_cache_object_mutation(zend_object *obj) +{ + zend_opcache_static_cache_tracked_dependency *dependency; + zend_opcache_static_cache_static_slot_handle *handle; + zend_opcache_static_cache_prepare_memo_dependency *memo_dependency; + + if (obj != NULL && zend_opcache_static_cache_has_prepare_memo_objects && zend_opcache_static_cache_prepare_memo_objects != NULL) { + memo_dependency = zend_hash_index_find_ptr(zend_opcache_static_cache_prepare_memo_objects, (zend_ulong) (uintptr_t) obj); + zend_opcache_static_cache_prepare_memo_mark_dependency_dirty(memo_dependency); + } + + if (obj == NULL || !zend_opcache_static_cache_has_tracked_objects) { + return; + } + + dependency = zend_opcache_static_cache_find_tracked_object(obj); + if (zend_opcache_static_cache_defer_dependency_publish(dependency)) { + return; + } + + handle = zend_opcache_static_cache_tracked_dependency_first_handle(dependency); + + if (!zend_opcache_static_cache_begin_mutation(handle, NULL)) { + return; + } + + zend_opcache_static_cache_flush_pending(); +} + +static void zend_opcache_static_cache_reference_update(zend_reference *ref) +{ + zend_opcache_static_cache_static_slot_handle *handle = zend_opcache_static_cache_find_tracked_reference(ref); + + if (handle == NULL || !zend_opcache_static_cache_kind_publishes_immediately(handle->kind)) { + return; + } + + if (handle->snapshot_root_published && zend_opcache_static_cache_has_mutable_immediate_root(handle)) { + return; + } + + zend_opcache_static_cache_publish_immediate_root(handle); +} + +static void zend_opcache_static_cache_init_class(zend_class_entry *ce) +{ + zend_opcache_static_cache_context *context; + zend_opcache_static_cache_context *previous_context; + zend_opcache_static_cache_class_blob_handle *class_blob_handle; + zend_opcache_static_cache_static_slot_handle *handle; + zend_opcache_static_cache_kind kind; + zend_string *property_name, *cache_key; + zend_property_info *property_info; + zend_long ttl; + zval cached_value, *slot; + bool should_track = false, fetched_cached; + + if (!zend_opcache_static_cache_attribute_classes_initialized || ce->name == NULL || CE_STATIC_MEMBERS(ce) == NULL) { + return; + } + + if (zend_opcache_static_cache_class_blob_enabled(ce)) { + class_blob_handle = zend_opcache_static_cache_ensure_class_blob_handle(ce); + if (class_blob_handle == NULL || !zend_opcache_static_cache_context_runtime(class_blob_handle->context)->available) { + return; + } + + EG(static_cache_class_access_active) = true; + + previous_context = zend_opcache_static_cache_activate_context(class_blob_handle->context); + if (zend_opcache_static_cache_rlock()) { + if (!class_blob_handle->initialized && zend_opcache_static_cache_header_is_initialized_locked()) { + zend_opcache_static_cache_refresh_class_blob_handle_locked(class_blob_handle); + } else if (!class_blob_handle->initialized) { + zend_opcache_static_cache_build_default_class_blob_state(class_blob_handle); + } + + zend_opcache_static_cache_unlock(); + } else if (!class_blob_handle->initialized) { + zend_opcache_static_cache_build_default_class_blob_state(class_blob_handle); + } + + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_install_class_blob_property_refs(class_blob_handle); + + return; + } + + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, property_name, property_info) { + if (property_name == NULL) { + continue; + } + + fetched_cached = false; + + kind = zend_opcache_static_cache_static_property_kind_and_ttl(ce, property_info, &ttl); + if (kind == ZEND_OPCACHE_STATIC_CACHE_NONE) { + continue; + } + + context = zend_opcache_static_cache_context_for_kind(kind); + if (!zend_opcache_static_cache_context_runtime(context)->available) { + continue; + } + + should_track = true; + + cache_key = zend_opcache_static_cache_static_property_key(ce, property_name, kind); + if (cache_key == NULL) { + continue; + } + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_rlock()) { + zend_opcache_static_cache_restore_context(previous_context); + zend_string_release(cache_key); + continue; + } + + if (!zend_opcache_static_cache_header_is_initialized_locked()) { + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + zend_string_release(cache_key); + continue; + } + + slot = CE_STATIC_MEMBERS(ce) + property_info->offset; + ZVAL_DEINDIRECT(slot); + + handle = zend_opcache_static_cache_ensure_tracked_root(slot, cache_key, context, kind, ttl, NULL); + zend_opcache_static_cache_capture_begin_for_handle(handle); + if (zend_opcache_static_cache_fetch_locked(cache_key, &cached_value, false, NULL, false)) { + fetched_cached = true; + zval_ptr_dtor(slot); + ZVAL_COPY_VALUE(slot, &cached_value); + zend_opcache_static_cache_set_tracked_root_original(handle); + handle->snapshot_root_published = zend_opcache_static_cache_has_mutable_immediate_root(handle); + handle->request_deferred_publish = zend_opcache_static_cache_kind_defers_reachable_mutation_publish(kind); + } + + zend_opcache_static_cache_capture_active = false; + + if (!fetched_cached) { + zend_opcache_static_cache_capture_discard(); + } + + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + zend_opcache_static_cache_register_tracked_value(slot, cache_key, kind, ttl); + zend_string_release(cache_key); + } ZEND_HASH_FOREACH_END(); + + if (should_track) { + zend_opcache_static_cache_track_attribute_class(ce); + } +} + +static void zend_opcache_static_cache_class_access(zend_class_entry *ce) +{ + zend_opcache_static_cache_class_blob_handle *class_blob_handle; + zend_opcache_static_cache_context *previous_context; + + if (ce == NULL || CE_STATIC_MEMBERS(ce) == NULL) { + return; + } + + /* Filter inline so that classes already memoized as ignored short-circuit + * without paying for an extra indirect call from the VM. */ + if (!zend_opcache_static_cache_class_access_filter(ce, &class_blob_handle)) { + return; + } + + if (class_blob_handle == NULL && zend_opcache_static_cache_class_blob_enabled(ce)) { + class_blob_handle = zend_opcache_static_cache_ensure_class_blob_handle(ce); + } + + if (class_blob_handle == NULL) { + zend_opcache_static_cache_init_class(ce); + return; + } + + if (!zend_opcache_static_cache_context_runtime(class_blob_handle->context)->available) { + return; + } + + previous_context = zend_opcache_static_cache_activate_context(class_blob_handle->context); + + if (zend_opcache_static_cache_rlock()) { + if (!class_blob_handle->initialized && zend_opcache_static_cache_header_is_initialized_locked()) { + zend_opcache_static_cache_refresh_class_blob_handle_locked(class_blob_handle); + } else if (!class_blob_handle->initialized) { + zend_opcache_static_cache_build_default_class_blob_state(class_blob_handle); + } + + zend_opcache_static_cache_unlock(); + } else if (!class_blob_handle->initialized) { + zend_opcache_static_cache_build_default_class_blob_state(class_blob_handle); + } + + zend_opcache_static_cache_restore_context(previous_context); + + zend_opcache_static_cache_install_class_blob_property_refs(class_blob_handle); +} + +static void zend_opcache_static_cache_class_update(zend_class_entry *ce) +{ + zend_opcache_static_cache_class_blob_handle *class_blob_handle; + + if (ce == NULL || CE_STATIC_MEMBERS(ce) == NULL) { + return; + } + + class_blob_handle = zend_opcache_static_cache_find_class_blob_handle(ce); + if (class_blob_handle == NULL) { + zend_opcache_static_cache_publish_persistent_static_properties_fast(ce); + + return; + } + + zend_opcache_static_cache_publish_class_blob_fast(class_blob_handle); +} + +static void zend_opcache_static_cache_init_function(zend_execute_data *execute_data) +{ + zend_opcache_static_cache_class_blob_handle *class_blob_handle; + zend_opcache_static_cache_kind kind; + zend_opcache_static_cache_context *context, *previous_context; + zend_opcache_static_cache_static_slot_handle *handle; + zend_op_array *op_array; + zend_string *var_name, *cache_key; + zend_long ttl; + zval cached_value, *slot; + HashTable *ht; + bool already_initialized = false, fetched_cached; + + if (!zend_opcache_static_cache_function_statics_initialized || + execute_data->func->type != ZEND_USER_FUNCTION + ) { + return; + } + + op_array = &execute_data->func->op_array; + /* Fast skip: if the op_array has neither function-level attributes nor a + * class scope carrying attributes, neither static-cache branch + * can fire. This avoids the per-BIND_STATIC has_attribute lookup for every + * ordinary `static $x` declaration. */ + if (op_array->attributes == NULL + && (op_array->scope == NULL || op_array->scope->attributes == NULL) + ) { + return; + } + + if (zend_opcache_static_cache_class_blob_applies_to_function_static(op_array)) { + ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr); + if (ht == NULL) { + ht = zend_array_dup(op_array->static_variables); + ZEND_MAP_PTR_SET(op_array->static_variables_ptr, ht); + } + + class_blob_handle = zend_opcache_static_cache_ensure_class_blob_handle(op_array->scope); + if (class_blob_handle == NULL || !zend_opcache_static_cache_context_runtime(class_blob_handle->context)->available) { + return; + } + + EG(static_cache_class_access_active) = true; + + previous_context = zend_opcache_static_cache_activate_context(class_blob_handle->context); + if (zend_opcache_static_cache_rlock()) { + if (!class_blob_handle->initialized && zend_opcache_static_cache_header_is_initialized_locked()) { + zend_opcache_static_cache_refresh_class_blob_handle_locked(class_blob_handle); + } else if (!class_blob_handle->initialized) { + zend_opcache_static_cache_build_default_class_blob_state(class_blob_handle); + } + + zend_opcache_static_cache_unlock(); + } else if (!class_blob_handle->initialized) { + zend_opcache_static_cache_build_default_class_blob_state(class_blob_handle); + } + + zend_opcache_static_cache_restore_context(previous_context); + + if (CE_STATIC_MEMBERS(op_array->scope) != NULL) { + zend_opcache_static_cache_install_class_blob_property_refs(class_blob_handle); + } + + zend_opcache_static_cache_bind_class_blob_method_refs(class_blob_handle, op_array, ht); + + return; + } + + if (!zend_opcache_static_cache_applies_to_function_static(op_array)) { + return; + } + + kind = zend_opcache_static_cache_op_array_kind_and_ttl(op_array, &ttl); + context = zend_opcache_static_cache_context_for_kind(kind); + if (!zend_opcache_static_cache_context_runtime(context)->available) { + return; + } + + ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr); + if (ht == NULL) { + ht = zend_array_dup(op_array->static_variables); + ZEND_MAP_PTR_SET(op_array->static_variables_ptr, ht); + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(ht, var_name, slot) { + if (var_name == NULL) { + continue; + } + + if (zend_hash_index_exists(&zend_opcache_static_cache_function_statics, (zend_ulong) (uintptr_t) slot)) { + already_initialized = true; + break; + } + } ZEND_HASH_FOREACH_END(); + + if (already_initialized) { + return; + } + + previous_context = zend_opcache_static_cache_activate_context(context); + + if (!zend_opcache_static_cache_rlock()) { + zend_opcache_static_cache_restore_context(previous_context); + + return; + } + + if (!zend_opcache_static_cache_header_is_initialized_locked()) { + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + + return; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(ht, var_name, slot) { + if (var_name == NULL) { + continue; + } + + fetched_cached = false; + + cache_key = zend_opcache_static_cache_function_variable_key(op_array, var_name, kind); + if (cache_key == NULL) { + continue; + } + + handle = zend_opcache_static_cache_ensure_tracked_root(slot, cache_key, context, kind, ttl, NULL); + zend_opcache_static_cache_capture_begin_for_handle(handle); + if (zend_opcache_static_cache_fetch_locked(cache_key, &cached_value, false, NULL, false)) { + fetched_cached = true; + zval_ptr_dtor(slot); + ZVAL_REF(slot, zend_opcache_static_cache_create_reference(&cached_value)); + zend_opcache_static_cache_set_tracked_root_original(handle); + handle->request_deferred_publish = zend_opcache_static_cache_kind_defers_reachable_mutation_publish(kind); + } + + zend_opcache_static_cache_capture_active = false; + + if (!fetched_cached) { + zend_opcache_static_cache_capture_discard(); + } + + zend_opcache_static_cache_track_function_static_slot(slot, cache_key, kind, ttl); + zend_opcache_static_cache_register_tracked_value(slot, cache_key, kind, ttl); + zend_string_release(cache_key); + } ZEND_HASH_FOREACH_END(); + + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); +} + +void zend_opcache_static_cache_update_mutation_hook_state(void) +{ + EG(tracked_mutation_hooks_active) = + zend_opcache_static_cache_has_tracked_arrays || + zend_opcache_static_cache_has_tracked_objects || + zend_opcache_static_cache_has_prepare_memo_arrays || + zend_opcache_static_cache_has_prepare_memo_objects + ; +} + +bool zend_opcache_static_cache_prepare_memo_fetch( + zval *value, + zend_opcache_static_cache_prepared_value *prepared) +{ + zend_opcache_static_cache_prepare_memo_entry *entry; + + if (value == NULL || prepared == NULL) { + return false; + } + + entry = zend_opcache_static_cache_prepare_memo_find_root_entry(value); + if (entry == NULL) { + return false; + } + + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH; + prepared->value_len = entry->value_len; + prepared->payload_size = entry->payload_size; + prepared->payload_used_size = entry->payload_used_size; + prepared->payload_source = entry->payload_buffer; + + return true; +} + +void zend_opcache_static_cache_prepare_memo_store( + zval *value, + zend_opcache_static_cache_prepared_value *prepared) +{ + zend_opcache_static_cache_prepare_memo_entry *entry; + zend_ulong key; + HashTable seen_arrays, seen_objects, *roots; + + if (value == NULL || + prepared == NULL || + prepared->value_type != ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH || + prepared->owned_buffer == NULL + ) { + return; + } + + ZVAL_DEREF(value); + if (Z_TYPE_P(value) != IS_ARRAY && Z_TYPE_P(value) != IS_OBJECT) { + return; + } + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&seen_objects, 8, NULL, NULL, 0); + if (!zend_opcache_static_cache_prepare_memo_can_track_value_recursive(value, &seen_arrays, &seen_objects)) { + zend_hash_destroy(&seen_objects); + zend_hash_destroy(&seen_arrays); + + return; + } + + zend_hash_destroy(&seen_objects); + zend_hash_destroy(&seen_arrays); + + if (zend_opcache_static_cache_prepare_memo_entries == NULL) { + zend_opcache_static_cache_prepare_memo_entries = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_prepare_memo_entries, 8, NULL, zend_opcache_static_cache_prepare_memo_entry_dtor, 0); + } + + if (zend_opcache_static_cache_prepare_memo_array_roots == NULL) { + zend_opcache_static_cache_prepare_memo_array_roots = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_prepare_memo_array_roots, 8, NULL, NULL, 0); + } + + if (zend_opcache_static_cache_prepare_memo_object_roots == NULL) { + zend_opcache_static_cache_prepare_memo_object_roots = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_prepare_memo_object_roots, 8, NULL, NULL, 0); + } + + if (zend_opcache_static_cache_prepare_memo_arrays == NULL) { + zend_opcache_static_cache_prepare_memo_arrays = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_prepare_memo_arrays, 8, NULL, zend_opcache_static_cache_prepare_memo_dependency_dtor, 0); + } + + if (zend_opcache_static_cache_prepare_memo_objects == NULL) { + zend_opcache_static_cache_prepare_memo_objects = emalloc(sizeof(HashTable)); + zend_hash_init(zend_opcache_static_cache_prepare_memo_objects, 8, NULL, zend_opcache_static_cache_prepare_memo_dependency_dtor, 0); + } + + entry = emalloc(sizeof(*entry)); + ZVAL_COPY_DEREF(&entry->root_snapshot, value); + entry->payload_buffer = prepared->owned_buffer; + entry->payload_size = prepared->payload_size; + entry->payload_used_size = prepared->payload_used_size; + entry->value_len = prepared->value_len; + entry->root_type = (uint8_t) Z_TYPE_P(value); + entry->dirty = false; + + prepared->owned_buffer = NULL; + prepared->payload_source = entry->payload_buffer; + + zend_hash_index_update_ptr( + zend_opcache_static_cache_prepare_memo_entries, + (zend_ulong) (uintptr_t) entry, + entry); + + roots = zend_opcache_static_cache_prepare_memo_root_table_for_value(value); + key = zend_opcache_static_cache_prepare_memo_root_key(value); + zend_hash_index_update_ptr(roots, key, entry); + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&seen_objects, 8, NULL, NULL, 0); + + zend_opcache_static_cache_prepare_memo_track_value_recursive(value, entry, &seen_arrays, &seen_objects); + zend_hash_destroy(&seen_objects); + zend_hash_destroy(&seen_arrays); + zend_opcache_static_cache_update_mutation_hook_state(); +} + +void zend_opcache_static_cache_delete_script_keys_locked(zend_persistent_script *persistent_script) +{ + zend_class_entry *ce; + + if (persistent_script == NULL) { + return; + } + + ZEND_HASH_MAP_FOREACH_PTR(&persistent_script->script.class_table, ce) { + zend_opcache_static_cache_delete_class_keys_locked(ce); + } ZEND_HASH_FOREACH_END(); +} + +void zend_opcache_static_cache_register_hooks(void) +{ + zend_class_init_statics_hook = zend_opcache_static_cache_init_class; + zend_function_init_statics_hook = zend_opcache_static_cache_init_function; + zend_class_static_access_hook = zend_opcache_static_cache_class_access; + zend_class_static_update_hook = zend_opcache_static_cache_class_update; + zend_tracked_reference_update_hook = zend_opcache_static_cache_reference_update; + zend_tracked_hash_mutation_hook = zend_opcache_static_cache_hash_mutation; + zend_tracked_object_mutation_hook = zend_opcache_static_cache_object_mutation; +} + +void zend_opcache_static_cache_unregister_hooks(void) +{ + if (zend_class_init_statics_hook == zend_opcache_static_cache_init_class) { + zend_class_init_statics_hook = NULL; + } + + if (zend_function_init_statics_hook == zend_opcache_static_cache_init_function) { + zend_function_init_statics_hook = NULL; + } + + if (zend_class_static_access_hook == zend_opcache_static_cache_class_access) { + zend_class_static_access_hook = NULL; + } + + if (zend_class_static_update_hook == zend_opcache_static_cache_class_update) { + zend_class_static_update_hook = NULL; + } + + if (zend_tracked_reference_update_hook == zend_opcache_static_cache_reference_update) { + zend_tracked_reference_update_hook = NULL; + } + + if (zend_tracked_hash_mutation_hook == zend_opcache_static_cache_hash_mutation) { + zend_tracked_hash_mutation_hook = NULL; + } + + if (zend_tracked_object_mutation_hook == zend_opcache_static_cache_object_mutation) { + zend_tracked_object_mutation_hook = NULL; + } +} + +void zend_opcache_static_cache_request_init(void) +{ + zend_hash_init(&zend_opcache_static_cache_attribute_classes, 8, NULL, NULL, 0); + zend_opcache_static_cache_attribute_classes_initialized = true; + zend_hash_init(&zend_opcache_static_cache_ignored_classes, 8, NULL, NULL, 0); + zend_opcache_static_cache_ignored_classes_initialized = true; + zend_hash_init(&zend_opcache_static_cache_function_statics, 8, NULL, zend_opcache_static_cache_function_static_entry_dtor, 0); + zend_opcache_static_cache_function_statics_initialized = true; + zend_hash_init(&zend_opcache_static_cache_class_blob_handles, 8, NULL, zend_opcache_static_cache_class_blob_handle_dtor, 0); + zend_opcache_static_cache_class_blob_handles_initialized = true; + zend_opcache_static_cache_init_tracking(); +} + +void zend_opcache_static_cache_capture_begin_for_handle(zend_opcache_static_cache_static_slot_handle *handle) +{ + zend_opcache_static_cache_capture_clear(); + if (!zend_opcache_static_cache_tracks_reachable_mutations(handle)) { + return; + } + + zend_opcache_static_cache_capture_handle = handle; + zend_opcache_static_cache_capture_active = true; +} + +void zend_opcache_static_cache_capture_discard(void) +{ + zend_opcache_static_cache_capture_active = false; + zend_opcache_static_cache_capture_clear(); +} + +void zend_opcache_static_cache_capture_decoded_reachable_value(zval *value) +{ + HashTable seen_arrays, seen_objects; + + if (!zend_opcache_static_cache_capture_active || value == NULL) { + return; + } + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + zend_hash_init(&seen_objects, 8, NULL, NULL, 0); + + zend_opcache_static_cache_capture_decoded_reachable_value_ex(value, &seen_arrays, &seen_objects); + + zend_hash_destroy(&seen_objects); + zend_hash_destroy(&seen_arrays); +} + +void zend_opcache_static_cache_capture_decoded_value(zval *value) +{ + zend_ulong key; + HashTable *table; + + if (!zend_opcache_static_cache_capture_active || value == NULL) { + return; + } + + ZVAL_DEREF(value); + switch (Z_TYPE_P(value)) { + case IS_ARRAY: + table = zend_opcache_static_cache_capture_ensure_table(&zend_opcache_static_cache_capture_arrays); + key = (zend_ulong) (uintptr_t) Z_ARRVAL_P(value); + + if (zend_opcache_static_cache_capture_handle != NULL) { + zend_hash_index_update_ptr(table, key, zend_opcache_static_cache_capture_handle); + } else { + zend_hash_index_add_empty_element(table, key); + } + + zend_opcache_static_cache_capture_available = true; + + return; + case IS_OBJECT: + table = zend_opcache_static_cache_capture_ensure_table(&zend_opcache_static_cache_capture_objects); + key = (zend_ulong) (uintptr_t) Z_OBJ_P(value); + + if (zend_opcache_static_cache_capture_handle != NULL) { + zend_hash_index_update_ptr(table, key, zend_opcache_static_cache_capture_handle); + } else { + zend_hash_index_add_empty_element(table, key); + } + + zend_opcache_static_cache_capture_available = true; + + return; + default: + return; + } +} + +void zend_opcache_static_cache_request_shutdown(void) +{ + if (zend_opcache_static_cache_attribute_classes_initialized || zend_opcache_static_cache_function_statics_initialized || zend_opcache_static_cache_class_blob_handles_initialized) { + zend_opcache_static_cache_flush_pending(); + zend_opcache_static_cache_publish_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_publish_context(&zend_opcache_static_cache_volatile_context_state); + + if (zend_opcache_static_cache_attribute_classes_initialized) { + zend_hash_destroy(&zend_opcache_static_cache_attribute_classes); + zend_opcache_static_cache_attribute_classes_initialized = false; + } + + if (zend_opcache_static_cache_ignored_classes_initialized) { + zend_hash_destroy(&zend_opcache_static_cache_ignored_classes); + zend_opcache_static_cache_ignored_classes_initialized = false; + } + + if (zend_opcache_static_cache_function_statics_initialized) { + zend_hash_destroy(&zend_opcache_static_cache_function_statics); + zend_opcache_static_cache_function_statics_initialized = false; + } + + if (zend_opcache_static_cache_class_blob_handles_initialized) { + zend_hash_destroy(&zend_opcache_static_cache_class_blob_handles); + zend_opcache_static_cache_class_blob_handles_initialized = false; + } + } + + zend_opcache_static_cache_destroy_tracking(); +} diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c new file mode 100644 index 000000000000..904ccfd70109 --- /dev/null +++ b/ext/opcache/zend_static_cache_storage.c @@ -0,0 +1,1565 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + +#include "zend_static_cache_internal.h" + +#ifndef ZEND_WIN32 +# include +# include +# include +# include +# ifdef ZTS +# include +# endif +# ifdef HAVE_UNISTD_H +# include +# endif +# if defined(__linux__) && defined(HAVE_MEMFD_CREATE) +# include +# endif +#endif + +static const zend_shared_memory_handler_entry zend_opcache_static_cache_handler_table[] = { +#ifdef USE_MMAP + { "mmap", &zend_alloc_mmap_handlers }, +#endif +#ifdef USE_SHM + { "shm", &zend_alloc_shm_handlers }, +#endif +#ifdef USE_SHM_OPEN + { "posix", &zend_alloc_posix_handlers }, +#endif +#ifdef ZEND_WIN32 + { "win32", &zend_alloc_win32_handlers }, +#endif + { NULL, NULL } +}; + +#ifndef ZEND_WIN32 +static ZEND_EXT_TLS zend_ulong zend_opcache_static_cache_entry_lock_owner_pid = 0; +#ifdef ZTS +static ZEND_EXT_TLS bool zend_opcache_static_cache_entry_locks_process_is_fork_child = false; +#endif +#endif + +static zend_always_inline bool zend_opcache_static_cache_force_startup_failure(void) +{ + const char *value = getenv("OPCACHE_STATIC_CACHE_FORCE_STARTUP_FAILURE"); + + return value != NULL && value[0] != '\0' && value[0] != '0'; +} + +static zend_always_inline bool zend_opcache_static_cache_requires_pre_request_storage(void) +{ + return sapi_module.name != NULL && strcmp(sapi_module.name, "fpm-fcgi") == 0; +} + +static zend_always_inline void zend_opcache_static_cache_set_unavailable(const char *failure_reason, bool startup_failed) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); + + runtime->available = false; + runtime->startup_failed = startup_failed; + runtime->backend_initialized = context->storage.initialized; + runtime->failure_reason = failure_reason; +} + +static zend_always_inline void zend_opcache_static_cache_set_available(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); + + runtime->available = true; + runtime->startup_failed = false; + runtime->backend_initialized = context->storage.initialized; + runtime->failure_reason = NULL; +} + +static zend_always_inline void zend_opcache_static_cache_cleanup_segments(const zend_shared_memory_handlers *handler, zend_shared_segment **segments, int segment_count) +{ + int index; + + if (!handler || !segments) { + return; + } + + for (index = 0; index < segment_count; index++) { + if (segments[index]->p && segments[index]->p != (void *) -1) { + handler->detach_segment(segments[index]); + } + } + + free(segments); +} + +#ifndef ZEND_WIN32 +#ifdef ZTS +static void zend_opcache_static_cache_entry_locks_shutdown(zend_opcache_static_cache_storage *storage) +{ + uint32_t index; + + if (!storage->entry_locks_initialized) { + return; + } + + for (index = 0; index < ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES; index++) { + if (storage->entry_locks[index] != NULL) { + tsrm_mutex_free(storage->entry_locks[index]); + storage->entry_locks[index] = NULL; + } + } + + storage->entry_locks_initialized = false; +} + +static bool zend_opcache_static_cache_entry_locks_startup(zend_opcache_static_cache_storage *storage) +{ + uint32_t index; + + if (storage->entry_locks_initialized) { + return true; + } + + for (index = 0; index < ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES; index++) { + storage->entry_locks[index] = tsrm_mutex_alloc(); + if (storage->entry_locks[index] == NULL) { + zend_opcache_static_cache_entry_locks_shutdown(storage); + + return false; + } + } + storage->entry_locks_initialized = true; + + return true; +} +#endif + +static bool zend_opcache_static_cache_lock_internal(short lock_type) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + struct flock mem_lock; + + if (!storage->lock_initialized) { + return false; + } + +#ifdef ZTS + zend_opcache_static_cache_zts_lock_is_write = lock_type == F_WRLCK; + if (!(lock_type == F_RDLCK + ? zend_thread_rwlock_rdlock(&storage->zts_lock) + : zend_thread_rwlock_wrlock(&storage->zts_lock) + ) + ) { + zend_opcache_static_cache_zts_lock_is_write = false; + + return false; + } +#endif + + mem_lock.l_type = lock_type; + mem_lock.l_whence = SEEK_SET; + mem_lock.l_start = 0; + mem_lock.l_len = 1; + + while (fcntl(storage->lock_file, F_SETLKW, &mem_lock) == -1) { + if (errno != EINTR) { +#ifdef ZTS + if (lock_type == F_RDLCK) { + zend_thread_rwlock_unlock_rd(&storage->zts_lock); + } else { + zend_thread_rwlock_unlock_wr(&storage->zts_lock); + } + zend_opcache_static_cache_zts_lock_is_write = false; +#endif + return false; + } + } + + return true; +} + +static bool zend_opcache_static_cache_lock_startup(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_storage *storage = &context->storage; + int val; + + if (storage->lock_initialized) { + return true; + } + +#ifdef ZTS + if (!zend_opcache_static_cache_entry_locks_startup(storage)) { + return false; + } + + if (!zend_thread_rwlock_init(&storage->zts_lock)) { + zend_opcache_static_cache_entry_locks_shutdown(storage); + + return false; + } +#endif + +#if defined(__linux__) && defined(HAVE_MEMFD_CREATE) && defined(MFD_CLOEXEC) + storage->lock_file = memfd_create(context->lock_name, MFD_CLOEXEC); + if (storage->lock_file >= 0) { + storage->lock_initialized = true; + return true; + } +#endif + +#ifdef O_TMPFILE + storage->lock_file = open(ZCG(accel_directives).lockfile_path, O_RDWR | O_TMPFILE | O_EXCL | O_CLOEXEC, 0666); + if (storage->lock_file >= 0) { + storage->lock_initialized = true; + return true; + } +#endif + + snprintf( + storage->lockfile_name, + sizeof(storage->lockfile_name), + "%s/%sXXXXXX", + ZCG(accel_directives).lockfile_path, + context->sem_filename_prefix + ); + + storage->lock_file = mkstemp(storage->lockfile_name); + if (storage->lock_file == -1) { +#ifdef ZTS + zend_thread_rwlock_destroy(&storage->zts_lock); + zend_opcache_static_cache_entry_locks_shutdown(storage); +#endif + + return false; + } + + if (fchmod(storage->lock_file, 0666) == -1) { + close(storage->lock_file); + storage->lock_file = -1; +#ifdef ZTS + zend_thread_rwlock_destroy(&storage->zts_lock); + zend_opcache_static_cache_entry_locks_shutdown(storage); +#endif + return false; + } + + val = fcntl(storage->lock_file, F_GETFD, 0); + val |= FD_CLOEXEC; + fcntl(storage->lock_file, F_SETFD, val); + unlink(storage->lockfile_name); + + storage->lock_initialized = true; + + return true; +} + +static void zend_opcache_static_cache_lock_shutdown(void) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + + if (!storage->lock_initialized) { + return; + } + + if (storage->lock_file >= 0) { + close(storage->lock_file); + storage->lock_file = -1; + } +#ifdef ZTS + zend_thread_rwlock_destroy(&storage->zts_lock); + zend_opcache_static_cache_entry_locks_shutdown(storage); +#endif + storage->lock_initialized = false; +} + +static bool zend_opcache_static_cache_rlock_impl(void) +{ + return zend_opcache_static_cache_lock_internal(F_RDLCK); +} + +static bool zend_opcache_static_cache_wlock_impl(void) +{ + return zend_opcache_static_cache_lock_internal(F_WRLCK); +} + +static void zend_opcache_static_cache_unlock_impl(void) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + struct flock mem_unlock; +#ifdef ZTS + bool zts_lock_is_write = zend_opcache_static_cache_zts_lock_is_write; +#endif + + if (!storage->lock_initialized) { + return; + } + + mem_unlock.l_type = F_UNLCK; + mem_unlock.l_whence = SEEK_SET; + mem_unlock.l_start = 0; + mem_unlock.l_len = 1; + fcntl(storage->lock_file, F_SETLK, &mem_unlock); + +#ifdef ZTS + if (zts_lock_is_write) { + zend_thread_rwlock_unlock_wr(&storage->zts_lock); + } else { + zend_thread_rwlock_unlock_rd(&storage->zts_lock); + } + zend_opcache_static_cache_zts_lock_is_write = false; +#endif +} +#else +static bool zend_opcache_static_cache_lock_startup(void) +{ + return false; +} + +static void zend_opcache_static_cache_lock_shutdown(void) +{ +} + +static bool zend_opcache_static_cache_rlock_impl(void) +{ + return false; +} + +static bool zend_opcache_static_cache_wlock_impl(void) +{ + return false; +} + +static void zend_opcache_static_cache_unlock_impl(void) +{ +} +#endif + +static HashTable **zend_opcache_static_cache_entry_locks_ptr_for_context(zend_opcache_static_cache_context *context) +{ + return context == &zend_opcache_static_cache_persistent_context_state + ? &zend_opcache_static_cache_persistent_entry_locks + : &zend_opcache_static_cache_volatile_entry_locks + ; +} + +static uint32_t *zend_opcache_static_cache_entry_lock_counts_for_context(zend_opcache_static_cache_context *context) +{ + return context == &zend_opcache_static_cache_persistent_context_state + ? zend_opcache_static_cache_persistent_entry_lock_counts + : zend_opcache_static_cache_volatile_entry_lock_counts + ; +} + +static uint32_t zend_opcache_static_cache_entry_lock_stripe(zend_string *key) +{ + return (uint32_t) (zend_string_hash_val(key) % ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES); +} + +#ifdef ZTS +static void zend_opcache_static_cache_entry_locks_reinit_after_fork(zend_opcache_static_cache_storage *storage) +{ + uint32_t index, allocated = 0; + MUTEX_T old_locks[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; + MUTEX_T new_locks[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; + + if (!storage->entry_locks_initialized) { + return; + } + + memcpy(old_locks, storage->entry_locks, sizeof(old_locks)); + + /* A child process may inherit a process-local heap mutex whose copied + * state says it was locked by the parent. Replace the child-side copies + * instead of trying to unlock or reuse them. */ + for (index = 0; index < ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES; index++) { + new_locks[index] = tsrm_mutex_alloc(); + if (new_locks[index] == NULL) { + while (allocated != 0) { + tsrm_mutex_free(new_locks[--allocated]); + } + for (index = 0; index < ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES; index++) { + if (old_locks[index] != NULL) { + tsrm_mutex_free(old_locks[index]); + } + } + memset(storage->entry_locks, 0, sizeof(storage->entry_locks)); + storage->entry_locks_initialized = false; + + return; + } + allocated++; + } + + for (index = 0; index < ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES; index++) { + if (old_locks[index] != NULL) { + tsrm_mutex_free(old_locks[index]); + } + } + + memcpy(storage->entry_locks, new_locks, sizeof(storage->entry_locks)); +} +#endif + +static void zend_opcache_static_cache_release_entry_lock_context(HashTable **locks_ptr, uint32_t *counts) +{ + if (*locks_ptr == NULL) { + return; + } + + zend_hash_destroy(*locks_ptr); + FREE_HASHTABLE(*locks_ptr); + *locks_ptr = NULL; + memset(counts, 0, sizeof(uint32_t) * ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES); +} + +static void zend_opcache_static_cache_ensure_entry_lock_process(void) +{ +#ifndef ZEND_WIN32 + zend_ulong current_pid = (zend_ulong) getpid(); + + if (zend_opcache_static_cache_entry_lock_owner_pid == 0) { + zend_opcache_static_cache_entry_lock_owner_pid = current_pid; + + return; + } + + if (zend_opcache_static_cache_entry_lock_owner_pid == current_pid) { + return; + } + + /* The request-local lock tables were inherited across fork. Destroy the + * child copies without unlocking stripes owned by the parent process. */ + zend_opcache_static_cache_release_entry_lock_context( + &zend_opcache_static_cache_volatile_entry_locks, + zend_opcache_static_cache_volatile_entry_lock_counts); + zend_opcache_static_cache_release_entry_lock_context( + &zend_opcache_static_cache_persistent_entry_locks, + zend_opcache_static_cache_persistent_entry_lock_counts); +#ifdef ZTS + zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_volatile_context_state.storage); + zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_persistent_context_state.storage); + zend_opcache_static_cache_entry_locks_process_is_fork_child = true; +#endif + zend_opcache_static_cache_entry_lock_owner_pid = current_pid; +#endif +} + +#ifndef ZEND_WIN32 +static bool zend_opcache_static_cache_lock_entry_stripe_ex(zend_opcache_static_cache_context *context, uint32_t stripe, bool blocking) +{ + zend_opcache_static_cache_storage *storage = &context->storage; + uint32_t *counts = zend_opcache_static_cache_entry_lock_counts_for_context(context); + struct flock mem_lock; +#ifdef ZTS + int result; +#endif + + if (counts[stripe] != 0) { + counts[stripe]++; + + return true; + } + +#ifdef ZTS + if (!storage->entry_locks_initialized && !zend_opcache_static_cache_entry_locks_startup(storage)) { + return false; + } + + if (storage->entry_locks[stripe] == NULL) { + return false; + } + + result = blocking + ? pthread_mutex_lock(storage->entry_locks[stripe]) + : pthread_mutex_trylock(storage->entry_locks[stripe]) + ; + if (result != 0) { + return false; + } +#endif + + if (!storage->lock_initialized || storage->lock_file < 0) { +#ifdef ZTS + pthread_mutex_unlock(storage->entry_locks[stripe]); +#endif + return false; + } + + mem_lock.l_type = F_WRLCK; + mem_lock.l_whence = SEEK_SET; + mem_lock.l_start = (off_t) stripe + 1; + mem_lock.l_len = 1; + + while (fcntl(storage->lock_file, blocking ? F_SETLKW : F_SETLK, &mem_lock) == -1) { + if (errno != EINTR) { +#ifdef ZTS + pthread_mutex_unlock(storage->entry_locks[stripe]); +#endif + return false; + } + } + + counts[stripe] = 1; + + return true; +} + +static bool zend_opcache_static_cache_lock_entry_stripe(zend_opcache_static_cache_context *context, uint32_t stripe) +{ + return zend_opcache_static_cache_lock_entry_stripe_ex(context, stripe, true); +} + +static bool zend_opcache_static_cache_try_lock_entry_stripe(zend_opcache_static_cache_context *context, uint32_t stripe) +{ + return zend_opcache_static_cache_lock_entry_stripe_ex(context, stripe, false); +} + +static void zend_opcache_static_cache_unlock_entry_stripe(zend_opcache_static_cache_context *context, uint32_t stripe) +{ + zend_opcache_static_cache_storage *storage = &context->storage; + uint32_t *counts = zend_opcache_static_cache_entry_lock_counts_for_context(context); + struct flock mem_unlock; + + ZEND_ASSERT(counts[stripe] != 0); + if (--counts[stripe] != 0) { + return; + } + + if (storage->lock_initialized && storage->lock_file >= 0) { + mem_unlock.l_type = F_UNLCK; + mem_unlock.l_whence = SEEK_SET; + mem_unlock.l_start = (off_t) stripe + 1; + mem_unlock.l_len = 1; + fcntl(storage->lock_file, F_SETLK, &mem_unlock); + } + +#ifdef ZTS + if (storage->entry_locks_initialized && storage->entry_locks[stripe] != NULL) { + tsrm_mutex_unlock(storage->entry_locks[stripe]); + } +#endif +} +#endif + +static void zend_opcache_static_cache_entry_lock_dtor(zval *lock_zv) +{ + zend_opcache_static_cache_entry_lock *lock = Z_PTR_P(lock_zv); + + if (lock == NULL) { + return; + } + +#ifndef ZEND_WIN32 + if (lock->owner_pid == (zend_ulong) getpid()) { + zend_opcache_static_cache_unlock_entry_stripe(lock->context, lock->stripe); + } +#endif + efree(lock); +} + +static HashTable *zend_opcache_static_cache_entry_locks(zend_opcache_static_cache_context *context) +{ + HashTable **locks_ptr = zend_opcache_static_cache_entry_locks_ptr_for_context(context); + + if (*locks_ptr == NULL) { + ALLOC_HASHTABLE(*locks_ptr); + zend_hash_init(*locks_ptr, 0, NULL, zend_opcache_static_cache_entry_lock_dtor, 0); + } + + return *locks_ptr; +} + +static uint32_t zend_opcache_static_cache_calculate_capacity(size_t size) +{ + size_t capacity = size / ZEND_OPCACHE_STATIC_CACHE_SLOT_BYTES, data_offset; + + if (capacity < ZEND_OPCACHE_STATIC_CACHE_MIN_CAPACITY) { + capacity = ZEND_OPCACHE_STATIC_CACHE_MIN_CAPACITY; + } else if (capacity > ZEND_OPCACHE_STATIC_CACHE_MAX_CAPACITY) { + capacity = ZEND_OPCACHE_STATIC_CACHE_MAX_CAPACITY; + } + + if ((capacity & 1) == 0) { + capacity--; + } + + for (;;) { + data_offset = ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_header) + capacity * sizeof(zend_opcache_static_cache_entry)); + if (data_offset < size || capacity == ZEND_OPCACHE_STATIC_CACHE_MIN_CAPACITY) { + break; + } + + capacity >>= 1; + if (capacity < ZEND_OPCACHE_STATIC_CACHE_MIN_CAPACITY) { + capacity = ZEND_OPCACHE_STATIC_CACHE_MIN_CAPACITY; + } + + if ((capacity & 1) == 0) { + capacity--; + } + } + + return (uint32_t) capacity; +} + +static uint32_t zend_opcache_static_cache_used_end_offset_locked(const zend_opcache_static_cache_header *header) +{ + return header->data_offset + header->next_free; +} + +static void zend_opcache_static_cache_free_list_remove_locked(zend_opcache_static_cache_header *header, uint32_t block_offset) +{ + zend_opcache_static_cache_block *block = zend_opcache_static_cache_block_ptr(block_offset); + + if (block->prev_free != 0) { + zend_opcache_static_cache_block_ptr(block->prev_free)->next_free = block->next_free; + } else { + header->free_list = block->next_free; + } + + if (block->next_free != 0) { + zend_opcache_static_cache_block_ptr(block->next_free)->prev_free = block->prev_free; + } + + block->next_free = 0; + block->prev_free = 0; + block->flags &= ~ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE; +} + +static void zend_opcache_static_cache_free_list_insert_locked(zend_opcache_static_cache_header *header, uint32_t block_offset) +{ + zend_opcache_static_cache_block *block = zend_opcache_static_cache_block_ptr(block_offset); + + block->prev_free = 0; + block->next_free = header->free_list; + + if (header->free_list != 0) { + zend_opcache_static_cache_block_ptr(header->free_list)->prev_free = block_offset; + } + + zend_opcache_static_cache_block_mark_free(block); + header->free_list = block_offset; +} + +static void zend_opcache_static_cache_update_following_prev_size_locked( + zend_opcache_static_cache_header *header, + uint32_t block_offset, + const zend_opcache_static_cache_block *block +) +{ + uint32_t next_offset = block_offset + block->size; + + if (next_offset < zend_opcache_static_cache_used_end_offset_locked(header)) { + zend_opcache_static_cache_block_ptr(next_offset)->prev_size = block->size; + } +} + +static void zend_opcache_static_cache_trim_tail_free_blocks_locked( + zend_opcache_static_cache_header *header, + uint32_t block_offset +) +{ + zend_opcache_static_cache_block *block = zend_opcache_static_cache_block_ptr(block_offset); + uint32_t prev_offset; + + while (block_offset >= header->data_offset && + header->last_block_offset == block_offset && + zend_opcache_static_cache_block_is_free(block) && + block_offset + block->size == zend_opcache_static_cache_used_end_offset_locked(header) + ) { + prev_offset = 0; + zend_opcache_static_cache_free_list_remove_locked(header, block_offset); + header->next_free -= block->size; + if (block->prev_size != 0 && block_offset > header->data_offset) { + prev_offset = block_offset - block->prev_size; + } + + header->last_block_offset = prev_offset; + if (prev_offset == 0) { + break; + } + + block_offset = prev_offset; + block = zend_opcache_static_cache_block_ptr(block_offset); + } +} + +static bool zend_opcache_static_cache_try_handler(const zend_shared_memory_handler_entry *handler_entry) +{ + const char *error_in = NULL; + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); + zend_opcache_static_cache_storage *storage = &context->storage; + zend_shared_segment **segments = NULL; + int segment_count = 0, result; + + result = handler_entry->handler->create_segments( + runtime->configured_memory, + &segments, + &segment_count, + &error_in + ); + if (result != ALLOC_SUCCESS) { + zend_opcache_static_cache_cleanup_segments(handler_entry->handler, segments, segment_count); + return false; + } + + storage->handler = handler_entry->handler; + storage->segments = segments; + storage->segment_count = segment_count; + storage->size = runtime->configured_memory; + storage->model = handler_entry->name; + storage->initialized = true; + + return true; +} + +static bool zend_opcache_static_cache_startup_storage(void) +{ + const zend_shared_memory_handler_entry *handler_entry; + const char *requested_model = ZCG(accel_directives).memory_model; + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + + if (storage->initialized) { + return true; + } + + if (requested_model && requested_model[0]) { + if (strcmp(requested_model, "cgi") == 0) { + requested_model = "shm"; + } + + for (handler_entry = zend_opcache_static_cache_handler_table; handler_entry->name; handler_entry++) { + if (strcmp(requested_model, handler_entry->name) == 0 && zend_opcache_static_cache_try_handler(handler_entry)) { + goto storage_ready; + } + } + } + + for (handler_entry = zend_opcache_static_cache_handler_table; handler_entry->name; handler_entry++) { + if (requested_model && requested_model[0] && strcmp(requested_model, handler_entry->name) == 0) { + continue; + } + + if (zend_opcache_static_cache_try_handler(handler_entry)) { + goto storage_ready; + } + } + + return false; + +storage_ready: + if (storage->segment_count != 1) { + zend_opcache_static_cache_cleanup_segments( + storage->handler, + storage->segments, + storage->segment_count + ); + zend_opcache_static_cache_reset_storage(); + + return false; + } + + if (!zend_opcache_static_cache_lock_startup()) { + zend_opcache_static_cache_cleanup_segments( + storage->handler, + storage->segments, + storage->segment_count + ); + zend_opcache_static_cache_reset_storage(); + + return false; + } + + if (!zend_opcache_static_cache_wlock()) { + zend_opcache_static_cache_lock_shutdown(); + zend_opcache_static_cache_cleanup_segments( + storage->handler, + storage->segments, + storage->segment_count + ); + zend_opcache_static_cache_reset_storage(); + + return false; + } + + if (!zend_opcache_static_cache_header_init_locked()) { + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_lock_shutdown(); + zend_opcache_static_cache_cleanup_segments( + storage->handler, + storage->segments, + storage->segment_count + ); + zend_opcache_static_cache_reset_storage(); + + return false; + } + + zend_opcache_static_cache_unlock(); + + return true; +} + +static bool zend_opcache_static_cache_payload_size_to_block_size(size_t size, uint32_t *block_size) +{ + size_t aligned_size; + + if (size == 0 || size > UINT32_MAX - sizeof(zend_opcache_static_cache_block)) { + return false; + } + + aligned_size = ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + size); + if (aligned_size > UINT32_MAX) { + return false; + } + + *block_size = (uint32_t) aligned_size; + + return true; +} + +static bool zend_opcache_static_cache_offset_in_block(uint32_t offset, uint32_t block_offset, uint32_t block_size) +{ + return offset >= block_offset + sizeof(zend_opcache_static_cache_block) && offset < block_offset + block_size; +} + +static bool zend_opcache_static_cache_block_is_movable_locked( + zend_opcache_static_cache_header *header, + uint32_t block_offset, + uint32_t block_size +) +{ + zend_opcache_static_cache_entry *entries, *entry; + uint32_t index; + bool referenced = false; + + entries = zend_opcache_static_cache_entries(header); + for (index = 0; index < header->capacity; index++) { + entry = &entries[index]; + if (entry->state != ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED) { + continue; + } + + if (entry->key_offset != 0 && + zend_opcache_static_cache_offset_in_block(entry->key_offset, block_offset, block_size) + ) { + referenced = true; + } + + if (entry->value_offset != 0 && + zend_opcache_static_cache_offset_in_block(entry->value_offset, block_offset, block_size) + ) { + referenced = true; + if (entry->value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { + /* Shared graphs may contain final-buffer pointers, so keep their block anchored. */ + return false; + } + } + } + + return referenced; +} + +static void zend_opcache_static_cache_update_moved_block_entries_locked( + zend_opcache_static_cache_header *header, + uint32_t old_block_offset, + uint32_t new_block_offset, + uint32_t block_size +) +{ + zend_opcache_static_cache_entry *entries, *entry; + uint32_t index, delta; + + ZEND_ASSERT(new_block_offset <= old_block_offset); + delta = old_block_offset - new_block_offset; + entries = zend_opcache_static_cache_entries(header); + for (index = 0; index < header->capacity; index++) { + entry = &entries[index]; + if (entry->state != ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED) { + continue; + } + + if (entry->key_offset != 0 && + zend_opcache_static_cache_offset_in_block(entry->key_offset, old_block_offset, block_size) + ) { + entry->key_offset -= delta; + } + + if (entry->value_offset != 0 && + zend_opcache_static_cache_offset_in_block(entry->value_offset, old_block_offset, block_size) + ) { + entry->value_offset -= delta; + } + } +} + +static bool zend_opcache_static_cache_compaction_can_fit_locked( + zend_opcache_static_cache_header *header, + uint32_t required_block_size +) +{ + zend_opcache_static_cache_block *block; + uint32_t data_end, used_end, offset, next_offset, block_size, region_start, region_used_size, write_offset, max_free_size = 0, region_free_size; + bool movable, would_move = false; + + data_end = header->data_offset + header->data_size; + used_end = header->data_offset + header->next_free; + offset = header->data_offset; + region_start = header->data_offset; + region_used_size = 0; + write_offset = header->data_offset; + + while (offset < used_end) { + block = zend_opcache_static_cache_block_ptr(offset); + block_size = block->size; + if (block_size < ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + 1) || + block_size > used_end - offset + ) { + return false; + } + + next_offset = offset + block_size; + if (!zend_opcache_static_cache_block_is_free(block)) { + movable = zend_opcache_static_cache_block_is_movable_locked(header, offset, block_size); + if (movable) { + if (offset != write_offset) { + would_move = true; + } + region_used_size += block_size; + write_offset += block_size; + } else { + if (offset - region_start < region_used_size) { + return false; + } + + region_free_size = offset - region_start - region_used_size; + + if (region_free_size > max_free_size) { + max_free_size = region_free_size; + } + + region_start = next_offset; + region_used_size = 0; + write_offset = next_offset; + } + } + + offset = next_offset; + } + + if (data_end - region_start < region_used_size) { + return false; + } + + if (data_end - region_start - region_used_size > max_free_size) { + max_free_size = data_end - region_start - region_used_size; + } + + return would_move && max_free_size >= required_block_size; +} + +void zend_opcache_static_cache_reset_runtime(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); + + memset(runtime, 0, sizeof(*runtime)); + + runtime->configured_memory = context == &zend_opcache_static_cache_persistent_context_state + ? ZCG(accel_directives).static_cache_persistent_size_mb + : ZCG(accel_directives).static_cache_volatile_size_mb + ; + runtime->enabled = runtime->configured_memory != 0; + + if (zend_opcache_static_cache_subsystem_disabled) { + runtime->enabled = false; + runtime->available = false; + runtime->startup_failed = true; + runtime->backend_initialized = context->storage.initialized; + runtime->failure_reason = zend_opcache_static_cache_subsystem_failure_reason; + } +} + +void zend_opcache_static_cache_reset_storage(void) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + + memset(storage, 0, sizeof(*storage)); +#ifndef ZEND_WIN32 + storage->lock_file = -1; +#endif +} + +bool zend_opcache_static_cache_header_init_locked(void) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + uint32_t capacity, data_offset; + + if (!header) { + return false; + } + + if (header->magic == ZEND_OPCACHE_STATIC_CACHE_MAGIC && header->version == ZEND_OPCACHE_STATIC_CACHE_VERSION) { + return true; + } + + capacity = zend_opcache_static_cache_calculate_capacity(storage->size); + data_offset = (uint32_t) ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_header) + capacity * sizeof(zend_opcache_static_cache_entry)); + if (data_offset >= storage->size) { + return false; + } + + /* Only the header and entry table need an eager zero state. Payload pages + * are touched on demand when allocator blocks are first created. */ + memset(header, 0, data_offset); + header->capacity = capacity; + header->data_offset = data_offset; + header->data_size = (uint32_t) (storage->size - data_offset); + header->next_free = 0; + header->free_list = 0; + header->last_block_offset = 0; + header->count = 0; + header->mutation_epoch = 1; + header->magic = ZEND_OPCACHE_STATIC_CACHE_MAGIC; + header->version = ZEND_OPCACHE_STATIC_CACHE_VERSION; + + return true; +} + +void zend_opcache_static_cache_free_locked(uint32_t payload_offset) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + zend_opcache_static_cache_block *block, *adjacent; + uint32_t block_offset, original_block_offset, next_offset, prev_offset; + + if (!header || payload_offset < sizeof(zend_opcache_static_cache_block)) { + return; + } + + block_offset = payload_offset - (uint32_t) sizeof(zend_opcache_static_cache_block); + original_block_offset = block_offset; + block = zend_opcache_static_cache_block_ptr(block_offset); + if (zend_opcache_static_cache_block_is_free(block)) { + return; + } + + zend_opcache_static_cache_block_mark_free(block); + + next_offset = block_offset + block->size; + if (next_offset < zend_opcache_static_cache_used_end_offset_locked(header)) { + adjacent = zend_opcache_static_cache_block_ptr(next_offset); + + if (zend_opcache_static_cache_block_is_free(adjacent)) { + zend_opcache_static_cache_free_list_remove_locked(header, next_offset); + block->size += adjacent->size; + if (header->last_block_offset == next_offset) { + header->last_block_offset = block_offset; + } + } + } + + if (block->prev_size != 0 && block_offset > header->data_offset) { + prev_offset = block_offset - block->prev_size; + + adjacent = zend_opcache_static_cache_block_ptr(prev_offset); + if (zend_opcache_static_cache_block_is_free(adjacent)) { + zend_opcache_static_cache_free_list_remove_locked(header, prev_offset); + block->size += adjacent->size; + adjacent->size = block->size; + block = adjacent; + block_offset = prev_offset; + if (header->last_block_offset == original_block_offset) { + header->last_block_offset = block_offset; + } + } + } + + zend_opcache_static_cache_update_following_prev_size_locked(header, block_offset, block); + zend_opcache_static_cache_free_list_insert_locked(header, block_offset); + zend_opcache_static_cache_trim_tail_free_blocks_locked(header, block_offset); +} + +uint32_t zend_opcache_static_cache_alloc_locked(size_t size, const void *source) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + zend_opcache_static_cache_block *block, *remainder; + uint32_t total_size, min_split_size, best_offset = 0, best_size = UINT32_MAX, block_offset, *free_offset_ptr; + size_t aligned_size; + + min_split_size = (uint32_t) ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + 1); + + if (!header || size == 0 || size > UINT32_MAX - sizeof(zend_opcache_static_cache_block)) { + return 0; + } + + aligned_size = ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + size); + if (aligned_size > UINT32_MAX) { + return 0; + } + + total_size = (uint32_t) aligned_size; + + free_offset_ptr = &header->free_list; + while (*free_offset_ptr != 0) { + block = zend_opcache_static_cache_block_ptr(*free_offset_ptr); + if (block->size >= total_size && block->size < best_size) { + best_offset = *free_offset_ptr; + best_size = block->size; + if (best_size == total_size) { + break; + } + } + free_offset_ptr = &block->next_free; + } + + if (best_offset != 0) { + block = zend_opcache_static_cache_block_ptr(best_offset); + zend_opcache_static_cache_free_list_remove_locked(header, best_offset); + if (block->size >= total_size + min_split_size) { + remainder = zend_opcache_static_cache_block_ptr(best_offset + total_size); + remainder->size = block->size - total_size; + remainder->prev_size = total_size; + remainder->next_free = 0; + remainder->prev_free = 0; + remainder->flags = ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE; + block->size = total_size; + zend_opcache_static_cache_update_following_prev_size_locked(header, best_offset + total_size, remainder); + zend_opcache_static_cache_free_list_insert_locked(header, best_offset + total_size); + if (header->last_block_offset == best_offset) { + header->last_block_offset = best_offset + total_size; + } + } else { + zend_opcache_static_cache_update_following_prev_size_locked(header, best_offset, block); + } + zend_opcache_static_cache_block_mark_used(block); + if (source != NULL) { + memcpy(zend_opcache_static_cache_ptr(best_offset + sizeof(zend_opcache_static_cache_block)), source, size); + } + + return best_offset + (uint32_t) sizeof(zend_opcache_static_cache_block); + } + + if (header->next_free > header->data_size || total_size > header->data_size - header->next_free) { + return 0; + } + + block_offset = header->data_offset + header->next_free; + block = zend_opcache_static_cache_block_ptr(block_offset); + block->size = total_size; + block->prev_size = header->last_block_offset != 0 ? zend_opcache_static_cache_block_ptr(header->last_block_offset)->size : 0; + block->next_free = 0; + block->prev_free = 0; + block->flags = 0; + if (source != NULL) { + memcpy(zend_opcache_static_cache_ptr(block_offset + sizeof(zend_opcache_static_cache_block)), source, size); + } + header->next_free += total_size; + header->last_block_offset = block_offset; + + return block_offset + (uint32_t) sizeof(zend_opcache_static_cache_block); +} + +bool zend_opcache_static_cache_compact_to_fit_locked(size_t size) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + zend_opcache_static_cache_block *block, *free_block; + uint32_t required_block_size, used_end, offset, next_offset, block_size, write_offset, + previous_block_size = 0, last_block_offset = 0, free_size; + bool moved = false, movable; + + if (!header || !zend_opcache_static_cache_header_init_locked()) { + return false; + } + + if (!zend_opcache_static_cache_payload_size_to_block_size(size, &required_block_size) || + required_block_size > header->data_size || + !zend_opcache_static_cache_compaction_can_fit_locked(header, required_block_size) + ) { + return false; + } + + used_end = header->data_offset + header->next_free; + offset = header->data_offset; + write_offset = header->data_offset; + header->free_list = 0; + + while (offset < used_end) { + block = zend_opcache_static_cache_block_ptr(offset); + block_size = block->size; + if (block_size < ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + 1) || + block_size > used_end - offset + ) { + return false; + } + + next_offset = offset + block_size; + if (zend_opcache_static_cache_block_is_free(block)) { + offset = next_offset; + continue; + } + + movable = zend_opcache_static_cache_block_is_movable_locked(header, offset, block_size); + if (!movable) { + if (write_offset < offset) { + free_block = zend_opcache_static_cache_block_ptr(write_offset); + free_size = offset - write_offset; + + free_block->size = free_size; + free_block->prev_size = previous_block_size; + free_block->next_free = 0; + free_block->prev_free = 0; + free_block->flags = ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE; + zend_opcache_static_cache_free_list_insert_locked(header, write_offset); + previous_block_size = free_size; + last_block_offset = write_offset; + } + + block->prev_size = previous_block_size; + block->next_free = 0; + block->prev_free = 0; + zend_opcache_static_cache_block_mark_used(block); + previous_block_size = block_size; + last_block_offset = offset; + write_offset = next_offset; + offset = next_offset; + continue; + } + + if (write_offset != offset) { + memmove(zend_opcache_static_cache_ptr(write_offset), block, block_size); + zend_opcache_static_cache_update_moved_block_entries_locked(header, offset, write_offset, block_size); + block = zend_opcache_static_cache_block_ptr(write_offset); + moved = true; + } + + block->prev_size = previous_block_size; + block->next_free = 0; + block->prev_free = 0; + zend_opcache_static_cache_block_mark_used(block); + previous_block_size = block_size; + last_block_offset = write_offset; + write_offset += block_size; + offset = next_offset; + } + + header->next_free = write_offset - header->data_offset; + header->last_block_offset = last_block_offset; + + if (moved) { + zend_opcache_static_cache_bump_mutation_epoch_locked(header); + } + + return moved; +} + +bool zend_opcache_static_cache_startup_storage_before_request(void) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + + if (zend_opcache_static_cache_force_startup_failure()) { + zend_opcache_static_cache_set_unavailable("Unable to initialize shared memory backend", true); + + return false; + } + + if (!zend_opcache_static_cache_startup_storage()) { + zend_opcache_static_cache_set_unavailable("Unable to initialize shared memory backend", true); + + return false; + } + + storage->initialized_before_request = true; + + return true; +} + +void zend_opcache_static_cache_shutdown_storage(void) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + + zend_opcache_static_cache_lock_shutdown(); + zend_opcache_static_cache_cleanup_segments( + storage->handler, + storage->segments, + storage->segment_count + ); + zend_opcache_static_cache_reset_storage(); +} + +void zend_opcache_static_cache_ensure_ready(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_runtime *runtime; + zend_opcache_static_cache_storage *storage = &context->storage; + + zend_opcache_static_cache_reset_runtime(); + runtime = zend_opcache_static_cache_context_runtime(context); + if (!runtime->enabled) { + return; + } + + if (!ZCG(enabled)) { + zend_opcache_static_cache_set_unavailable("OPcache is disabled", false); + + return; + } + + if (!accel_startup_ok) { + zend_opcache_static_cache_set_unavailable("OPcache startup failed", true); + + return; + } + + if (file_cache_only) { + zend_opcache_static_cache_set_unavailable("Cache is unavailable in file_cache_only mode", false); + + return; + } + + if (!storage->initialized && + zend_opcache_static_cache_requires_pre_request_storage() + ) { + zend_opcache_static_cache_set_unavailable("Shared memory backend was not initialized before FPM worker startup", true); + + return; + } + + if (!zend_opcache_static_cache_startup_storage()) { + zend_opcache_static_cache_set_unavailable("Unable to initialize shared memory backend", true); + + return; + } + + if (zend_opcache_static_cache_requires_pre_request_storage() && + !storage->initialized_before_request + ) { + zend_opcache_static_cache_set_unavailable("Shared memory backend was initialized after FPM worker startup", true); + + return; + } + + zend_opcache_static_cache_set_available(); +} + +void zend_opcache_static_cache_populate_array(zval *return_value) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); + zend_opcache_static_cache_storage *storage = &context->storage; + zend_opcache_static_cache_header *header; + zend_long entry_count = 0; + + array_init(return_value); + if (runtime->available && zend_opcache_static_cache_rlock()) { + header = zend_opcache_static_cache_header_ptr(); + + if (zend_opcache_static_cache_header_is_initialized_locked()) { + entry_count = header->count; + } + + zend_opcache_static_cache_unlock(); + } + + add_assoc_bool(return_value, "enabled", runtime->enabled); + add_assoc_bool(return_value, "available", runtime->available); + add_assoc_bool(return_value, "startup_failed", runtime->startup_failed); + add_assoc_bool(return_value, "backend_initialized", runtime->backend_initialized); + add_assoc_long(return_value, "configured_memory", (zend_long) runtime->configured_memory); + add_assoc_long(return_value, "shared_memory", (zend_long) storage->size); + add_assoc_long(return_value, "entry_count", entry_count); + add_assoc_long(return_value, "segment_count", storage->segment_count); + add_assoc_string(return_value, "shared_model", storage->model ? (char *) storage->model : ""); + + if (runtime->failure_reason) { + add_assoc_string(return_value, "failure_reason", (char *) runtime->failure_reason); + } +} + +bool zend_opcache_static_cache_rlock(void) +{ + return zend_opcache_static_cache_rlock_impl(); +} + +bool zend_opcache_static_cache_wlock(void) +{ + return zend_opcache_static_cache_wlock_impl(); +} + +void zend_opcache_static_cache_unlock(void) +{ + zend_opcache_static_cache_unlock_impl(); +} + +bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_entry_lock *lock; + HashTable **locks_ptr = zend_opcache_static_cache_entry_locks_ptr_for_context(context); + uint32_t stripe = zend_opcache_static_cache_entry_lock_stripe(key); + + zend_opcache_static_cache_ensure_entry_lock_process(); + + if (*locks_ptr != NULL && zend_hash_exists(*locks_ptr, key)) { + return true; + } + +#ifndef ZEND_WIN32 + if (!zend_opcache_static_cache_lock_entry_stripe(context, stripe)) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); + + return false; + } +#else + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); + + return false; +#endif + + lock = emalloc(sizeof(zend_opcache_static_cache_entry_lock)); + lock->context = context; + lock->owner_pid = +#ifndef ZEND_WIN32 + (zend_ulong) getpid(); +#else + 0; +#endif + lock->stripe = stripe; + + if (zend_hash_add_ptr(zend_opcache_static_cache_entry_locks(context), key, lock) == NULL) { +#ifndef ZEND_WIN32 + zend_opcache_static_cache_unlock_entry_stripe(context, stripe); +#endif + efree(lock); + + return true; + } + + return true; +} + +bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_entry_lock *lock; + HashTable **locks_ptr = zend_opcache_static_cache_entry_locks_ptr_for_context(context); + uint32_t stripe = zend_opcache_static_cache_entry_lock_stripe(key); + + zend_opcache_static_cache_ensure_entry_lock_process(); + + if (*locks_ptr != NULL && zend_hash_exists(*locks_ptr, key)) { + return true; + } + +#ifndef ZEND_WIN32 + if (!zend_opcache_static_cache_try_lock_entry_stripe(context, stripe)) { + return false; + } +#else + return false; +#endif + + lock = emalloc(sizeof(zend_opcache_static_cache_entry_lock)); + lock->context = context; + lock->owner_pid = +#ifndef ZEND_WIN32 + (zend_ulong) getpid(); +#else + 0; +#endif + lock->stripe = stripe; + + if (zend_hash_add_ptr(zend_opcache_static_cache_entry_locks(context), key, lock) == NULL) { +#ifndef ZEND_WIN32 + zend_opcache_static_cache_unlock_entry_stripe(context, stripe); +#endif + efree(lock); + + return true; + } + + return true; +} + +bool zend_opcache_static_cache_has_entry_lock(zend_string *key) +{ + HashTable **locks_ptr = zend_opcache_static_cache_entry_locks_ptr_for_context(zend_opcache_static_cache_active_context()); + + zend_opcache_static_cache_ensure_entry_lock_process(); + + return *locks_ptr != NULL && zend_hash_exists(*locks_ptr, key); +} + +void zend_opcache_static_cache_release_entry_lock(zend_string *key) +{ + HashTable **locks_ptr = zend_opcache_static_cache_entry_locks_ptr_for_context(zend_opcache_static_cache_active_context()); + + zend_opcache_static_cache_ensure_entry_lock_process(); + + if (*locks_ptr == NULL) { + return; + } + + zend_hash_del(*locks_ptr, key); +} + +bool zend_opcache_static_cache_has_all_entry_locks(void) +{ + uint32_t stripe, *counts = zend_opcache_static_cache_entry_lock_counts_for_context(zend_opcache_static_cache_active_context()); + + zend_opcache_static_cache_ensure_entry_lock_process(); + + for (stripe = 0; stripe < ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES; stripe++) { + if (counts[stripe] == 0) { + return false; + } + } + + return true; +} + +void zend_opcache_static_cache_release_active_entry_locks(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + + zend_opcache_static_cache_release_entry_lock_context( + zend_opcache_static_cache_entry_locks_ptr_for_context(context), + zend_opcache_static_cache_entry_lock_counts_for_context(context)); +} + +void zend_opcache_static_cache_release_request_entry_locks(void) +{ + zend_opcache_static_cache_release_entry_lock_context( + &zend_opcache_static_cache_volatile_entry_locks, + zend_opcache_static_cache_volatile_entry_lock_counts + ); + zend_opcache_static_cache_release_entry_lock_context( + &zend_opcache_static_cache_persistent_entry_locks, + zend_opcache_static_cache_persistent_entry_lock_counts + ); +#if !defined(ZEND_WIN32) && defined(ZTS) + if (zend_opcache_static_cache_entry_locks_process_is_fork_child) { + zend_opcache_static_cache_entry_locks_shutdown(&zend_opcache_static_cache_volatile_context_state.storage); + zend_opcache_static_cache_entry_locks_shutdown(&zend_opcache_static_cache_persistent_context_state.storage); + } +#endif +#ifndef ZEND_WIN32 + zend_opcache_static_cache_entry_lock_owner_pid = 0; +#endif +} diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 0105af776137..37c3c26858f1 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -19,6 +19,7 @@ #include "php.h" #include "ext/standard/php_var.h" #include "zend_smart_str.h" +#include "zend_execute.h" #include "zend_interfaces.h" #include "zend_exceptions.h" @@ -28,6 +29,8 @@ #include "spl_exceptions.h" #include "spl_functions.h" /* For spl_set_private_debug_info_property() */ +#include "ext/opcache/zend_static_cache.h" /* for zend_opcache_static_cache_safe_direct_handlers */ + /* Defined later in the file */ PHPAPI zend_class_entry *spl_ce_ArrayIterator; PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator; @@ -36,6 +39,9 @@ PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator; static zend_object_handlers spl_handler_ArrayObject; PHPAPI zend_class_entry *spl_ce_ArrayObject; +static void spl_array_object_serialize(zval *object, zval *return_value); +static void spl_array_object_unserialize(zval *object, HashTable *data); + typedef struct _spl_array_object { zval array; HashTable *sentinel_array; @@ -673,6 +679,7 @@ PHP_METHOD(ArrayObject, offsetSet) RETURN_THROWS(); } spl_array_write_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index, value); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(ZEND_THIS)); } /* }}} */ void spl_array_iterator_append(zval *object, zval *append_value) /* {{{ */ @@ -685,6 +692,7 @@ void spl_array_iterator_append(zval *object, zval *append_value) /* {{{ */ } spl_array_write_dimension(Z_OBJ_P(object), NULL, append_value); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ /* {{{ Appends the value (cannot be called for objects). */ @@ -706,6 +714,7 @@ PHP_METHOD(ArrayObject, offsetUnset) RETURN_THROWS(); } spl_array_unset_dimension_ex(0, Z_OBJ_P(ZEND_THIS), index); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(ZEND_THIS)); } /* }}} */ /* {{{ Return a copy of the contained array */ @@ -1027,6 +1036,7 @@ PHP_METHOD(ArrayObject, setIteratorClass) ZEND_PARSE_PARAMETERS_END(); intern->ce_get_iterator = ce_get_iterator; + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ @@ -1067,6 +1077,7 @@ PHP_METHOD(ArrayObject, setFlags) } intern->ar_flags = (intern->ar_flags & SPL_ARRAY_INT_MASK) | (ar_flags & ~SPL_ARRAY_INT_MASK); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ @@ -1087,6 +1098,7 @@ PHP_METHOD(ArrayObject, exchangeArray) RETVAL_ARR(zend_array_dup(spl_array_get_hash_table(intern))); spl_array_set_array(object, intern, array, 0L, true); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ @@ -1212,6 +1224,7 @@ static void spl_array_method(INTERNAL_FUNCTION_PARAMETERS, const char *fname, si ZVAL_NULL(ht_zv); zval_ptr_dtor(¶ms[0]); } + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(ZEND_THIS)); } /* }}} */ #define SPL_ARRAY_METHOD(cname, fname, use_arg) \ @@ -1390,11 +1403,16 @@ PHP_METHOD(ArrayObject, unserialize) /* {{{ */ PHP_METHOD(ArrayObject, __serialize) { - spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS); - zval tmp; - ZEND_PARSE_PARAMETERS_NONE(); + spl_array_object_serialize(ZEND_THIS, return_value); +} + +static void spl_array_object_serialize(zval *object, zval *return_value) +{ + spl_array_object *intern = Z_SPLARRAY_P(object); + zval tmp; + array_init(return_value); /* flags */ @@ -1428,15 +1446,24 @@ PHP_METHOD(ArrayObject, __serialize) /* {{{ */ PHP_METHOD(ArrayObject, __unserialize) { - spl_array_object *intern = Z_SPLARRAY_P(ZEND_THIS); HashTable *data; - zval *flags_zv, *storage_zv, *members_zv, *iterator_class_zv; - zend_long flags; if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &data) == FAILURE) { RETURN_THROWS(); } + spl_array_object_unserialize(ZEND_THIS, data); + if (EG(exception)) { + RETURN_THROWS(); + } +} + +static void spl_array_object_unserialize(zval *object, HashTable *data) +{ + spl_array_object *intern = Z_SPLARRAY_P(object); + zval *flags_zv, *storage_zv, *members_zv, *iterator_class_zv; + zend_long flags; + flags_zv = zend_hash_index_find(data, 0); storage_zv = zend_hash_index_find(data, 1); members_zv = zend_hash_index_find(data, 2); @@ -1448,7 +1475,7 @@ PHP_METHOD(ArrayObject, __unserialize) Z_TYPE_P(iterator_class_zv) != IS_STRING))) { zend_throw_exception(spl_ce_UnexpectedValueException, "Incomplete or ill-typed serialization data", 0); - RETURN_THROWS(); + return; } flags = Z_LVAL_P(flags_zv); @@ -1462,14 +1489,14 @@ PHP_METHOD(ArrayObject, __unserialize) if (Z_TYPE_P(storage_zv) != IS_OBJECT && Z_TYPE_P(storage_zv) != IS_ARRAY) { /* TODO Use UnexpectedValueException instead? And better error message? */ zend_throw_exception(spl_ce_InvalidArgumentException, "Passed variable is not an array or object", 0); - RETURN_THROWS(); + return; } - spl_array_set_array(ZEND_THIS, intern, storage_zv, 0L, true); + spl_array_set_array(object, intern, storage_zv, 0L, true); } object_properties_load(&intern->std, Z_ARRVAL_P(members_zv)); if (EG(exception)) { - RETURN_THROWS(); + return; } if (iterator_class_zv && Z_TYPE_P(iterator_class_zv) == IS_STRING) { @@ -1479,14 +1506,14 @@ PHP_METHOD(ArrayObject, __unserialize) zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Cannot deserialize ArrayObject with iterator class '%s'; no such class exists", ZSTR_VAL(Z_STR_P(iterator_class_zv))); - RETURN_THROWS(); + return; } if (!instanceof_function(ce, zend_ce_iterator)) { zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Cannot deserialize ArrayObject with iterator class '%s'; this class does not implement the Iterator interface", ZSTR_VAL(Z_STR_P(iterator_class_zv))); - RETURN_THROWS(); + return; } intern->ce_get_iterator = ce; @@ -1494,6 +1521,123 @@ PHP_METHOD(ArrayObject, __unserialize) } /* }}} */ +static bool spl_array_object_copy_direct_cache_state( + void *context, + zend_object *old_object, + zend_object *new_object, + zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value) +{ + zval old_zv, new_zv, state_zv, cloned_state_zv, empty_props_zv; + bool result = false; + + if (clone_value == NULL) { + return false; + } + + ZVAL_OBJ(&old_zv, old_object); + ZVAL_OBJ(&new_zv, new_object); + ZVAL_UNDEF(&state_zv); + ZVAL_UNDEF(&cloned_state_zv); + + spl_array_object_serialize(&old_zv, &state_zv); + if (EG(exception) || Z_TYPE(state_zv) != IS_ARRAY) { + goto cleanup; + } + + /* Properties are restored by the caller after all internal state zvals + * have been copied through the shared graph clone context. */ + array_init(&empty_props_zv); + zend_hash_index_update(Z_ARRVAL(state_zv), 2, &empty_props_zv); + + if (!clone_value(context, &cloned_state_zv, &state_zv) || + Z_TYPE(cloned_state_zv) != IS_ARRAY) { + goto cleanup; + } + + spl_array_object_unserialize(&new_zv, Z_ARRVAL(cloned_state_zv)); + result = !EG(exception); + +cleanup: + if (Z_TYPE(cloned_state_zv) != IS_UNDEF) { + zval_ptr_dtor(&cloned_state_zv); + } + if (Z_TYPE(state_zv) != IS_UNDEF) { + zval_ptr_dtor(&state_zv); + } + + return result; +} + +static bool spl_array_object_direct_cache_state_has_unstorable( + void *context, + const zval *object, + zend_opcache_static_cache_safe_direct_value_has_unstorable_func_t value_has_unstorable) +{ + zval state_zv; + bool result = false; + + if (value_has_unstorable == NULL) { + return false; + } + + ZVAL_UNDEF(&state_zv); + spl_array_object_serialize((zval *) object, &state_zv); + if (EG(exception) || Z_TYPE(state_zv) == IS_UNDEF) { + return true; + } + + result = value_has_unstorable(context, &state_zv); + zval_ptr_dtor(&state_zv); + + return result; +} + +static bool spl_array_object_serialize_direct_cache_state(const zval *object, zval *state) +{ + zval empty_props_zv; + + ZVAL_UNDEF(state); + spl_array_object_serialize((zval *) object, state); + if (EG(exception) || Z_TYPE_P(state) != IS_ARRAY) { + if (Z_TYPE_P(state) != IS_UNDEF) { + zval_ptr_dtor(state); + } + ZVAL_UNDEF(state); + + return false; + } + + ZVAL_UNDEF(&empty_props_zv); + array_init(&empty_props_zv); + zend_hash_index_update(Z_ARRVAL_P(state), 2, &empty_props_zv); + + return true; +} + +static bool spl_array_object_unserialize_direct_cache_state(zval *object, zval *state) +{ + if (Z_TYPE_P(state) != IS_ARRAY) { + return false; + } + + spl_array_object_unserialize(object, Z_ARRVAL_P(state)); + + return !EG(exception); +} + +const zend_opcache_static_cache_safe_direct_handlers *spl_array_object_get_direct_cache_handlers(void) +{ + static const zend_opcache_static_cache_safe_direct_handlers handlers = { + false, + spl_array_object_copy_direct_cache_state, + spl_array_object_direct_cache_state_has_unstorable, + spl_array_object_serialize_direct_cache_state, + spl_array_object_unserialize_direct_cache_state + }; + + return &handlers; +} + /* {{{ */ PHP_METHOD(ArrayObject, __debugInfo) { @@ -1678,6 +1822,7 @@ PHP_METHOD(ArrayIterator, rewind) ZEND_PARSE_PARAMETERS_NONE(); spl_array_rewind(intern); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ @@ -1703,6 +1848,7 @@ PHP_METHOD(ArrayIterator, seek) while (position-- > 0 && (result = spl_array_next(intern)) == SUCCESS); if (result == SUCCESS && zend_hash_has_more_elements_ex(aht, spl_array_get_pos_ptr(aht, intern)) == SUCCESS) { + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); return; /* ok */ } } @@ -1759,6 +1905,7 @@ PHP_METHOD(ArrayIterator, next) ZEND_PARSE_PARAMETERS_NONE(); spl_array_next_ex(intern, aht); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); } /* }}} */ diff --git a/ext/spl/spl_array.h b/ext/spl/spl_array.h index f99bb3f6fe88..10ca1b307366 100644 --- a/ext/spl/spl_array.h +++ b/ext/spl/spl_array.h @@ -29,6 +29,12 @@ extern PHPAPI zend_class_entry *spl_ce_ArrayObject; extern PHPAPI zend_class_entry *spl_ce_ArrayIterator; extern PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator; +#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; +#endif +const zend_opcache_static_cache_safe_direct_handlers *spl_array_object_get_direct_cache_handlers(void); + PHP_MINIT_FUNCTION(spl_array); extern void spl_array_iterator_append(zval *object, zval *append_value); diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index cfaf209a56e1..5a33171d508b 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -21,15 +21,21 @@ #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_attributes.h" +#include "zend_execute.h" #include "spl_fixedarray_arginfo.h" #include "spl_fixedarray.h" #include "spl_exceptions.h" #include "ext/json/php_json.h" /* For php_json_serializable_ce */ +#include "ext/opcache/zend_static_cache.h" /* for zend_opcache_static_cache_safe_direct_handlers */ + static zend_object_handlers spl_handler_SplFixedArray; PHPAPI zend_class_entry *spl_ce_SplFixedArray; +static void spl_fixedarray_object_serialize(zval *object, zval *return_value); +static void spl_fixedarray_object_unserialize(zval *object, HashTable *data); + /* Check if the object is an instance of a subclass of SplFixedArray that overrides method's implementation. * Expect subclassing SplFixedArray to be rare and check that first. */ #define HAS_FIXEDARRAY_ARRAYACCESS_OVERRIDE(object, method) UNEXPECTED((object)->ce != spl_ce_SplFixedArray && (object)->ce->arrayaccess_funcs_ptr->method->common.scope != spl_ce_SplFixedArray) @@ -581,12 +587,17 @@ PHP_METHOD(SplFixedArray, __wakeup) PHP_METHOD(SplFixedArray, __serialize) { - spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(ZEND_THIS); + ZEND_PARSE_PARAMETERS_NONE(); + + spl_fixedarray_object_serialize(ZEND_THIS, return_value); +} + +static void spl_fixedarray_object_serialize(zval *object, zval *return_value) +{ + spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(object); zval *current; zend_string *key; - ZEND_PARSE_PARAMETERS_NONE(); - HashTable *ht = zend_std_get_properties(&intern->std); uint32_t num_properties = zend_hash_num_elements(ht); array_init_size(return_value, intern->array.size + num_properties); @@ -612,16 +623,25 @@ PHP_METHOD(SplFixedArray, __serialize) PHP_METHOD(SplFixedArray, __unserialize) { - spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(ZEND_THIS); HashTable *data; - zval members_zv, *elem; - zend_string *key; - zend_long size; if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &data) == FAILURE) { RETURN_THROWS(); } + spl_fixedarray_object_unserialize(ZEND_THIS, data); + if (EG(exception)) { + RETURN_THROWS(); + } +} + +static void spl_fixedarray_object_unserialize(zval *object, HashTable *data) +{ + spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(object); + zval members_zv, *elem; + zend_string *key; + zend_long size; + if (intern->array.size == 0) { size = zend_hash_num_elements(data); spl_fixedarray_init_non_empty_struct(&intern->array, size); @@ -655,6 +675,141 @@ PHP_METHOD(SplFixedArray, __unserialize) } } +static bool spl_fixedarray_object_copy_direct_cache_state( + void *context, + zend_object *old_object, + zend_object *new_object, + zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value) +{ + zval old_zv, new_zv, state_zv, cloned_state_zv, elements_zv, copied_elem; + zval *elem; + zend_string *key; + bool result = false; + + if (clone_value == NULL) { + return false; + } + + ZVAL_OBJ(&old_zv, old_object); + ZVAL_OBJ(&new_zv, new_object); + ZVAL_UNDEF(&state_zv); + ZVAL_UNDEF(&cloned_state_zv); + + spl_fixedarray_object_serialize(&old_zv, &state_zv); + if (EG(exception) || Z_TYPE(state_zv) != IS_ARRAY) { + goto cleanup; + } + + /* SplFixedArray serializes elements and object properties into the same + * array. Properties are restored by the caller. */ + array_init_size(&elements_zv, zend_hash_num_elements(Z_ARRVAL(state_zv))); + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL(state_zv), key, elem) { + if (key == NULL) { + ZVAL_COPY(&copied_elem, elem); + zend_hash_next_index_insert(Z_ARRVAL(elements_zv), &copied_elem); + } + } ZEND_HASH_FOREACH_END(); + + zval_ptr_dtor(&state_zv); + ZVAL_COPY_VALUE(&state_zv, &elements_zv); + + if (!clone_value(context, &cloned_state_zv, &state_zv) || + Z_TYPE(cloned_state_zv) != IS_ARRAY) { + goto cleanup; + } + + spl_fixedarray_object_unserialize(&new_zv, Z_ARRVAL(cloned_state_zv)); + result = !EG(exception); + +cleanup: + if (Z_TYPE(cloned_state_zv) != IS_UNDEF) { + zval_ptr_dtor(&cloned_state_zv); + } + if (Z_TYPE(state_zv) != IS_UNDEF) { + zval_ptr_dtor(&state_zv); + } + + return result; +} + +static bool spl_fixedarray_object_direct_cache_state_has_unstorable( + void *context, + const zval *object, + zend_opcache_static_cache_safe_direct_value_has_unstorable_func_t value_has_unstorable) +{ + zval state_zv; + bool result = false; + + if (value_has_unstorable == NULL) { + return false; + } + + ZVAL_UNDEF(&state_zv); + spl_fixedarray_object_serialize((zval *) object, &state_zv); + if (EG(exception) || Z_TYPE(state_zv) == IS_UNDEF) { + return true; + } + + result = value_has_unstorable(context, &state_zv); + zval_ptr_dtor(&state_zv); + + return result; +} + +static bool spl_fixedarray_object_serialize_direct_cache_state(const zval *object, zval *state) +{ + zval serialized_state, copied_elem; + zval *elem; + zend_string *key; + + ZVAL_UNDEF(state); + ZVAL_UNDEF(&serialized_state); + spl_fixedarray_object_serialize((zval *) object, &serialized_state); + if (EG(exception) || Z_TYPE(serialized_state) != IS_ARRAY) { + if (Z_TYPE(serialized_state) != IS_UNDEF) { + zval_ptr_dtor(&serialized_state); + } + + return false; + } + + array_init_size(state, zend_hash_num_elements(Z_ARRVAL(serialized_state))); + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL(serialized_state), key, elem) { + if (key == NULL) { + ZVAL_COPY(&copied_elem, elem); + zend_hash_next_index_insert(Z_ARRVAL_P(state), &copied_elem); + } + } ZEND_HASH_FOREACH_END(); + + zval_ptr_dtor(&serialized_state); + + return true; +} + +static bool spl_fixedarray_object_unserialize_direct_cache_state(zval *object, zval *state) +{ + if (Z_TYPE_P(state) != IS_ARRAY) { + return false; + } + + spl_fixedarray_object_unserialize(object, Z_ARRVAL_P(state)); + + return !EG(exception); +} + +const zend_opcache_static_cache_safe_direct_handlers *spl_fixedarray_object_get_direct_cache_handlers(void) +{ + static const zend_opcache_static_cache_safe_direct_handlers handlers = { + false, + spl_fixedarray_object_copy_direct_cache_state, + spl_fixedarray_object_direct_cache_state_has_unstorable, + spl_fixedarray_object_serialize_direct_cache_state, + spl_fixedarray_object_unserialize_direct_cache_state + }; + + return &handlers; +} + PHP_METHOD(SplFixedArray, count) { zval *object = ZEND_THIS; @@ -788,6 +943,7 @@ PHP_METHOD(SplFixedArray, setSize) intern = Z_SPLFIXEDARRAY_P(object); spl_fixedarray_resize(&intern->array, size); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(object)); RETURN_TRUE; } @@ -838,6 +994,7 @@ PHP_METHOD(SplFixedArray, offsetSet) intern = Z_SPLFIXEDARRAY_P(ZEND_THIS); spl_fixedarray_object_write_dimension_helper(intern, zindex, value); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(ZEND_THIS)); } @@ -853,6 +1010,7 @@ PHP_METHOD(SplFixedArray, offsetUnset) intern = Z_SPLFIXEDARRAY_P(ZEND_THIS); spl_fixedarray_object_unset_dimension_helper(intern, zindex); + ZEND_MAYBE_TRACK_OBJECT_MUTATION(Z_OBJ_P(ZEND_THIS)); } diff --git a/ext/spl/spl_fixedarray.h b/ext/spl/spl_fixedarray.h index e0661a95d872..660a90b8bd11 100644 --- a/ext/spl/spl_fixedarray.h +++ b/ext/spl/spl_fixedarray.h @@ -16,8 +16,16 @@ #ifndef SPL_FIXEDARRAY_H #define SPL_FIXEDARRAY_H +#include "php.h" + extern PHPAPI zend_class_entry *spl_ce_SplFixedArray; +#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; +#endif +const zend_opcache_static_cache_safe_direct_handlers *spl_fixedarray_object_get_direct_cache_handlers(void); + PHP_MINIT_FUNCTION(spl_fixedarray); #endif /* SPL_FIXEDARRAY_H */ diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index 907988654337..65b0b0732dd4 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -548,7 +548,8 @@ class Tester $this->masterProcess = proc_open($cmd, $desc, $pipes, null, $envVars); register_shutdown_function( - function ($masterProcess) use ($configFile) { + /* Fix for LLVM 20 ASan false-positive */ + static function ($masterProcess) use ($configFile) { @unlink($configFile); if (is_resource($masterProcess)) { @proc_terminate($masterProcess); @@ -1154,6 +1155,8 @@ class Tester $this->terminate(); } proc_close($this->masterProcess); + /* Break the Tester -> Response -> Tester cycle before leak checkers run. */ + $this->response = null; } /** From 3ad9bfc1d506d0a3e795f48c7b0e23154d07ae4c Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 00:57:04 +0000 Subject: [PATCH 02/29] fix: remove benchmark PHPT --- .../static_cache_benchmark_scenarios_001.phpt | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 ext/opcache/tests/static_cache_benchmark_scenarios_001.phpt diff --git a/ext/opcache/tests/static_cache_benchmark_scenarios_001.phpt b/ext/opcache/tests/static_cache_benchmark_scenarios_001.phpt deleted file mode 100644 index 52da30a15e66..000000000000 --- a/ext/opcache/tests/static_cache_benchmark_scenarios_001.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -Benchmark scenario catalog exposes the vote-prep scenarios ---FILE-- - ---EXPECT-- -vote_read_long read 7 9 -carbon_datetime_compare read 2 9 -fetch_mutate_object read 1 3 -vote_write_throughput write 5 3 1 distinct 32 -vote_write_contention_shared write 5 3 5 shared 1 -vote_write_contention_distinct write 5 3 5 distinct 16 -vote_entry_reservation_contention write 2 3 5 shared 1 From 8a775523a1b724e0c3c1c6a9a2b8449103f63996 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 01:16:11 +0000 Subject: [PATCH 03/29] fix: trying fix windows build --- .../static_cache_windows_backend_001.phpt | 60 ++ ext/opcache/zend_static_cache.c | 2 +- ext/opcache/zend_static_cache_internal.h | 4 +- ext/opcache/zend_static_cache_storage.c | 618 +++++++++++++++++- 4 files changed, 656 insertions(+), 28 deletions(-) create mode 100644 ext/opcache/tests/static_cache_windows_backend_001.phpt diff --git a/ext/opcache/tests/static_cache_windows_backend_001.phpt b/ext/opcache/tests/static_cache_windows_backend_001.phpt new file mode 100644 index 000000000000..ed1003fd101e --- /dev/null +++ b/ext/opcache/tests/static_cache_windows_backend_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +OPcache static cache uses the Win32 shared memory and lock backend +--EXTENSIONS-- +opcache +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + 'volatile'])); +var_dump(OPcache\persistent_lock($key)); +OPcache\persistent_store($key, ['backend' => 'persistent']); + +var_dump(OPcache\volatile_fetch($key)); +var_dump(OPcache\persistent_fetch($key)); + +?> +--EXPECT-- +bool(true) +bool(true) +string(5) "win32" +string(5) "win32" +int(33554432) +int(33554432) +bool(true) +bool(true) +bool(true) +array(1) { + ["backend"]=> + string(8) "volatile" +} +array(1) { + ["backend"]=> + string(10) "persistent" +} diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index bb8ce7f2f1de..e3e14a3daac2 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -71,7 +71,7 @@ const char *zend_opcache_static_cache_subsystem_failure_reason = NULL; static HashTable zend_opcache_static_cache_safe_direct_handler_table; static bool zend_opcache_static_cache_safe_direct_handlers_initialized = false; -#if defined(ZTS) && !defined(ZEND_WIN32) +#ifdef ZTS ZEND_EXT_TLS bool zend_opcache_static_cache_zts_lock_is_write = false; #endif ZEND_EXT_TLS HashTable zend_opcache_static_cache_attribute_classes; diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index 236f88b9d0a8..36809e463f1f 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -137,7 +137,6 @@ typedef struct _zend_opcache_static_cache_storage { bool initialized; bool initialized_before_request; bool lock_initialized; -#ifndef ZEND_WIN32 int lock_file; char lockfile_name[MAXPATHLEN]; #ifdef ZTS @@ -145,7 +144,6 @@ typedef struct _zend_opcache_static_cache_storage { MUTEX_T entry_locks[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; bool entry_locks_initialized; #endif -#endif } zend_opcache_static_cache_storage; typedef struct _zend_opcache_static_cache_context { @@ -374,7 +372,7 @@ extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_class_blob_handles; extern ZEND_EXT_TLS bool zend_opcache_static_cache_class_blob_handles_initialized; extern bool zend_opcache_static_cache_safe_direct_classes_marked; extern ZEND_EXT_TLS zend_opcache_static_cache_context *zend_opcache_static_cache_active_context_ptr; -#if defined(ZTS) && !defined(ZEND_WIN32) +#ifdef ZTS extern ZEND_EXT_TLS bool zend_opcache_static_cache_zts_lock_is_write; #endif extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_roots; diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 904ccfd70109..32d2037b61e1 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -16,7 +16,15 @@ #include "zend_static_cache_internal.h" -#ifndef ZEND_WIN32 +#ifdef ZEND_WIN32 +# include "zend_execute.h" +# include "zend_system_id.h" +# include "win32/ioutil.h" +# include +# include +# include +# include +#else # include # include # include @@ -32,6 +40,322 @@ # endif #endif +#ifdef ZEND_WIN32 +# define ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE (2 * sizeof(void *)) +# define ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_NAME "ZendOPcache.StaticCache.SharedMemoryArea" +# define ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_MUTEX_NAME "ZendOPcache.StaticCache.SharedMemoryMutex" +# define ZEND_OPCACHE_STATIC_CACHE_WIN32_LOCK_FILE_NAME "ZendOPcache.StaticCache.LockFile" + +typedef struct _zend_opcache_static_cache_win32_segment { + zend_shared_segment segment; + HANDLE memfile; + void *mapping_base; + size_t mapping_size; +} zend_opcache_static_cache_win32_segment; + +static void zend_opcache_static_cache_win32_create_name(char *buffer, size_t buffer_size, const char *name, size_t unique_id) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + const char *sapi_name = sapi_module.name != NULL ? sapi_module.name : ""; + + snprintf( + buffer, + buffer_size, + "%s@%.32s@%.20s@%.32s@%s@%zx", + name, + accel_uname_id, + sapi_name, + zend_system_id, + context->lock_name, + unique_id + ); +} + +static bool zend_opcache_static_cache_win32_set_segment( + zend_opcache_static_cache_win32_segment *segment, + HANDLE memfile, + void *mapping_base, + size_t mapping_size, + size_t requested_size +) +{ + segment->memfile = memfile; + segment->mapping_base = mapping_base; + segment->mapping_size = mapping_size; + segment->segment.p = (char *) mapping_base + ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE; + segment->segment.pos = 0; + segment->segment.size = requested_size; + + return true; +} + +static int zend_opcache_static_cache_win32_reattach_segment( + zend_opcache_static_cache_win32_segment *segment, + HANDLE memfile, + size_t requested_size, + const char **error_in +) +{ + void *metadata_view, *wanted_mapping_base, *execute_ex_base, *mapping_base; + MEMORY_BASIC_INFORMATION info; + size_t mapping_size; + + if (requested_size > SIZE_MAX - ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE) { + *error_in = "size overflow"; + return ALLOC_FAILURE; + } + + mapping_size = requested_size + ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE; + metadata_view = MapViewOfFileEx(memfile, FILE_MAP_READ, 0, 0, ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE, NULL); + if (metadata_view == NULL) { + *error_in = "MapViewOfFileEx"; + return ALLOC_FAILURE; + } + + wanted_mapping_base = ((void **) metadata_view)[0]; + execute_ex_base = ((void **) metadata_view)[1]; + UnmapViewOfFile(metadata_view); + + if ((void *) execute_ex != execute_ex_base) { + *error_in = "execute_ex"; + return ALLOC_FAILURE; + } + + if (VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 || + info.State != MEM_FREE || + info.RegionSize < mapping_size + ) { + *error_in = "VirtualQuery"; + return ALLOC_FAILURE; + } + + mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, wanted_mapping_base); + if (mapping_base == NULL) { + *error_in = "MapViewOfFileEx"; + return ALLOC_FAILURE; + } + + return zend_opcache_static_cache_win32_set_segment(segment, memfile, mapping_base, mapping_size, requested_size) + ? ALLOC_SUCCESS + : ALLOC_FAILURE; +} + +static int zend_opcache_static_cache_win32_create_segment( + zend_opcache_static_cache_win32_segment *segment, + const char *mapping_name, + size_t requested_size, + const char **error_in +) +{ + HANDLE memfile; + void *mapping_base = NULL; + size_t mapping_size; + DWORD size_high, size_low; + uint32_t index; + void *configured_mapping_base = (void *) -1; +#if defined(_WIN64) + void *mapping_base_set[] = { + (void *) 0x0000100000000000, + (void *) 0x0000200000000000, + (void *) 0x0000300000000000, + (void *) 0x0000400000000000, + (void *) 0x0000500000000000, + (void *) 0x0000600000000000, + (void *) 0x0000700000000000, + NULL, + (void *) -1 + }; +#else + void *mapping_base_set[] = { + (void *) 0x20000000, + (void *) 0x21000000, + (void *) 0x30000000, + (void *) 0x31000000, + (void *) 0x50000000, + NULL, + (void *) -1 + }; +#endif + + if (requested_size > SIZE_MAX - ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE) { + *error_in = "size overflow"; + return ALLOC_FAILURE; + } + + mapping_size = requested_size + ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE; +#if defined(_WIN64) + size_high = (DWORD) (mapping_size >> 32); + size_low = (DWORD) (mapping_size & 0xffffffff); +#else + if (mapping_size > UINT32_MAX) { + *error_in = "size overflow"; + return ALLOC_FAILURE; + } + size_high = 0; + size_low = (DWORD) mapping_size; +#endif + + memfile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, size_high, size_low, mapping_name); + if (memfile == NULL) { + *error_in = "CreateFileMappingA"; + return ALLOC_FAILURE; + } + + if (GetLastError() == ERROR_ALREADY_EXISTS) { + int result = zend_opcache_static_cache_win32_reattach_segment(segment, memfile, requested_size, error_in); + if (result != ALLOC_SUCCESS) { + CloseHandle(memfile); + } + + return result; + } + + if (ZCG(accel_directives).mmap_base && *ZCG(accel_directives).mmap_base) { + char *mmap_base = ZCG(accel_directives).mmap_base; + + if (mmap_base[0] == '0' && mmap_base[1] == 'x') { + mmap_base += 2; + } + if (sscanf(mmap_base, "%p", &configured_mapping_base) == 1) { + mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, configured_mapping_base); + } + } + + for (index = 0; mapping_base == NULL && mapping_base_set[index] != (void *) -1; index++) { + mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, mapping_base_set[index]); + } + + if (mapping_base == NULL) { + CloseHandle(memfile); + *error_in = "MapViewOfFileEx"; + return ALLOC_FAILURE; + } + + ((void **) mapping_base)[0] = mapping_base; + ((void **) mapping_base)[1] = (void *) execute_ex; + + return zend_opcache_static_cache_win32_set_segment(segment, memfile, mapping_base, mapping_size, requested_size) + ? ALLOC_SUCCESS + : ALLOC_FAILURE; +} + +static int zend_opcache_static_cache_win32_create_segments( + size_t requested_size, + zend_shared_segment ***shared_segments_p, + int *shared_segments_count, + const char **error_in +) +{ + zend_opcache_static_cache_win32_segment *segment; + HANDLE mutex = NULL, memfile = NULL; + DWORD wait_result; + char mapping_name[MAXPATHLEN], mutex_name[MAXPATHLEN]; + bool mutex_acquired = false; + int result = ALLOC_FAILURE; + + *shared_segments_count = 1; + *shared_segments_p = (zend_shared_segment **) calloc( + 1, + sizeof(zend_shared_segment *) + sizeof(zend_opcache_static_cache_win32_segment) + ); + if (*shared_segments_p == NULL) { + *error_in = "calloc"; + return ALLOC_FAILURE; + } + + segment = (zend_opcache_static_cache_win32_segment *) ((char *) *shared_segments_p + sizeof(zend_shared_segment *)); + (*shared_segments_p)[0] = (zend_shared_segment *) segment; + + zend_opcache_static_cache_win32_create_name( + mapping_name, + sizeof(mapping_name), + ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_NAME, + requested_size + ); + zend_opcache_static_cache_win32_create_name( + mutex_name, + sizeof(mutex_name), + ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_MUTEX_NAME, + requested_size + ); + + mutex = CreateMutexA(NULL, FALSE, mutex_name); + if (mutex == NULL) { + *error_in = "CreateMutexA"; + goto failure; + } + + wait_result = WaitForSingleObject(mutex, INFINITE); + if (wait_result != WAIT_OBJECT_0 && wait_result != WAIT_ABANDONED) { + *error_in = "WaitForSingleObject"; + goto failure; + } + mutex_acquired = true; + + memfile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, mapping_name); + if (memfile != NULL) { + result = zend_opcache_static_cache_win32_reattach_segment(segment, memfile, requested_size, error_in); + if (result != ALLOC_SUCCESS) { + CloseHandle(memfile); + } + } else { + result = zend_opcache_static_cache_win32_create_segment(segment, mapping_name, requested_size, error_in); + } + + if (mutex_acquired) { + ReleaseMutex(mutex); + mutex_acquired = false; + } + CloseHandle(mutex); + mutex = NULL; + + if (result == ALLOC_SUCCESS) { + return ALLOC_SUCCESS; + } + +failure: + if (mutex_acquired) { + ReleaseMutex(mutex); + } + if (mutex != NULL) { + CloseHandle(mutex); + } + free(*shared_segments_p); + *shared_segments_p = NULL; + *shared_segments_count = 0; + + return ALLOC_FAILURE; +} + +static int zend_opcache_static_cache_win32_detach_segment(zend_shared_segment *shared_segment) +{ + zend_opcache_static_cache_win32_segment *segment = (zend_opcache_static_cache_win32_segment *) shared_segment; + + if (segment->mapping_base != NULL) { + UnmapViewOfFile(segment->mapping_base); + segment->mapping_base = NULL; + } + + if (segment->memfile != NULL) { + CloseHandle(segment->memfile); + segment->memfile = NULL; + } + + return 0; +} + +static size_t zend_opcache_static_cache_win32_segment_type_size(void) +{ + return sizeof(zend_opcache_static_cache_win32_segment); +} + +static const zend_shared_memory_handlers zend_opcache_static_cache_win32_handlers = { + zend_opcache_static_cache_win32_create_segments, + zend_opcache_static_cache_win32_detach_segment, + zend_opcache_static_cache_win32_segment_type_size +}; +#endif + static const zend_shared_memory_handler_entry zend_opcache_static_cache_handler_table[] = { #ifdef USE_MMAP { "mmap", &zend_alloc_mmap_handlers }, @@ -43,7 +367,7 @@ static const zend_shared_memory_handler_entry zend_opcache_static_cache_handler_ { "posix", &zend_alloc_posix_handlers }, #endif #ifdef ZEND_WIN32 - { "win32", &zend_alloc_win32_handlers }, + { "win32", &zend_opcache_static_cache_win32_handlers }, #endif { NULL, NULL } }; @@ -106,7 +430,6 @@ static zend_always_inline void zend_opcache_static_cache_cleanup_segments(const free(segments); } -#ifndef ZEND_WIN32 #ifdef ZTS static void zend_opcache_static_cache_entry_locks_shutdown(zend_opcache_static_cache_storage *storage) { @@ -148,6 +471,7 @@ static bool zend_opcache_static_cache_entry_locks_startup(zend_opcache_static_ca } #endif +#ifndef ZEND_WIN32 static bool zend_opcache_static_cache_lock_internal(short lock_type) { zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; @@ -325,27 +649,217 @@ static void zend_opcache_static_cache_unlock_impl(void) #endif } #else +static bool zend_opcache_static_cache_win32_open_lock_file_at(zend_opcache_static_cache_storage *storage, const char *directory, const char *base_name) +{ + if (directory == NULL || directory[0] == '\0') { + return false; + } + + snprintf( + storage->lockfile_name, + sizeof(storage->lockfile_name), + "%s/%s.lock", + directory, + base_name + ); + + storage->lock_file = php_win32_ioutil_open(storage->lockfile_name, O_RDWR | O_CREAT | O_BINARY, 0666); + + return storage->lock_file >= 0; +} + +static bool zend_opcache_static_cache_win32_open_lock_file(zend_opcache_static_cache_context *context) +{ + zend_opcache_static_cache_storage *storage = &context->storage; + char base_name[MAXPATHLEN], temp_path[MAXPATHLEN]; + DWORD temp_path_len; + + zend_opcache_static_cache_win32_create_name( + base_name, + sizeof(base_name), + ZEND_OPCACHE_STATIC_CACHE_WIN32_LOCK_FILE_NAME, + storage->size + ); + + if (zend_opcache_static_cache_win32_open_lock_file_at(storage, ZCG(accel_directives).lockfile_path, base_name)) { + return true; + } + + temp_path_len = GetTempPathA(sizeof(temp_path), temp_path); + if (temp_path_len == 0 || temp_path_len >= sizeof(temp_path)) { + return false; + } + + return zend_opcache_static_cache_win32_open_lock_file_at(storage, temp_path, base_name); +} + +static bool zend_opcache_static_cache_win32_lock_range(zend_opcache_static_cache_storage *storage, DWORD offset, bool exclusive, bool blocking) +{ + OVERLAPPED overlapped; + HANDLE file_handle; + DWORD flags = 0; + + if (!storage->lock_initialized || storage->lock_file < 0) { + return false; + } + + file_handle = (HANDLE) _get_osfhandle(storage->lock_file); + if (file_handle == INVALID_HANDLE_VALUE) { + return false; + } + + memset(&overlapped, 0, sizeof(overlapped)); + overlapped.Offset = offset; + + if (exclusive) { + flags |= LOCKFILE_EXCLUSIVE_LOCK; + } + if (!blocking) { + flags |= LOCKFILE_FAIL_IMMEDIATELY; + } + + return LockFileEx(file_handle, flags, 0, 1, 0, &overlapped) == TRUE; +} + +static void zend_opcache_static_cache_win32_unlock_range(zend_opcache_static_cache_storage *storage, DWORD offset) +{ + OVERLAPPED overlapped; + HANDLE file_handle; + + if (!storage->lock_initialized || storage->lock_file < 0) { + return; + } + + file_handle = (HANDLE) _get_osfhandle(storage->lock_file); + if (file_handle == INVALID_HANDLE_VALUE) { + return; + } + + memset(&overlapped, 0, sizeof(overlapped)); + overlapped.Offset = offset; + UnlockFileEx(file_handle, 0, 1, 0, &overlapped); +} + +static bool zend_opcache_static_cache_lock_internal(bool exclusive) +{ + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + + if (!storage->lock_initialized) { + return false; + } + +#ifdef ZTS + zend_opcache_static_cache_zts_lock_is_write = exclusive; + if (!(exclusive + ? zend_thread_rwlock_wrlock(&storage->zts_lock) + : zend_thread_rwlock_rdlock(&storage->zts_lock) + ) + ) { + zend_opcache_static_cache_zts_lock_is_write = false; + + return false; + } +#endif + + if (!zend_opcache_static_cache_win32_lock_range(storage, 0, exclusive, true)) { +#ifdef ZTS + if (exclusive) { + zend_thread_rwlock_unlock_wr(&storage->zts_lock); + } else { + zend_thread_rwlock_unlock_rd(&storage->zts_lock); + } + zend_opcache_static_cache_zts_lock_is_write = false; +#endif + return false; + } + + return true; +} + static bool zend_opcache_static_cache_lock_startup(void) { - return false; + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_storage *storage = &context->storage; + + if (storage->lock_initialized) { + return true; + } + +#ifdef ZTS + if (!zend_opcache_static_cache_entry_locks_startup(storage)) { + return false; + } + + if (!zend_thread_rwlock_init(&storage->zts_lock)) { + zend_opcache_static_cache_entry_locks_shutdown(storage); + + return false; + } +#endif + + if (!zend_opcache_static_cache_win32_open_lock_file(context)) { +#ifdef ZTS + zend_thread_rwlock_destroy(&storage->zts_lock); + zend_opcache_static_cache_entry_locks_shutdown(storage); +#endif + return false; + } + + storage->lock_initialized = true; + + return true; } static void zend_opcache_static_cache_lock_shutdown(void) { + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; + + if (!storage->lock_initialized) { + return; + } + + if (storage->lock_file >= 0) { + php_win32_ioutil_close(storage->lock_file); + storage->lock_file = -1; + } +#ifdef ZTS + zend_thread_rwlock_destroy(&storage->zts_lock); + zend_opcache_static_cache_entry_locks_shutdown(storage); +#endif + storage->lock_initialized = false; } static bool zend_opcache_static_cache_rlock_impl(void) { - return false; + return zend_opcache_static_cache_lock_internal(false); } static bool zend_opcache_static_cache_wlock_impl(void) { - return false; + return zend_opcache_static_cache_lock_internal(true); } static void zend_opcache_static_cache_unlock_impl(void) { + zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; +#ifdef ZTS + bool zts_lock_is_write = zend_opcache_static_cache_zts_lock_is_write; +#endif + + if (!storage->lock_initialized) { + return; + } + + zend_opcache_static_cache_win32_unlock_range(storage, 0); + +#ifdef ZTS + if (zts_lock_is_write) { + zend_thread_rwlock_unlock_wr(&storage->zts_lock); + } else { + zend_thread_rwlock_unlock_rd(&storage->zts_lock); + } + zend_opcache_static_cache_zts_lock_is_write = false; +#endif } #endif @@ -370,7 +884,7 @@ static uint32_t zend_opcache_static_cache_entry_lock_stripe(zend_string *key) return (uint32_t) (zend_string_hash_val(key) % ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES); } -#ifdef ZTS +#if defined(ZTS) && !defined(ZEND_WIN32) static void zend_opcache_static_cache_entry_locks_reinit_after_fork(zend_opcache_static_cache_storage *storage) { uint32_t index, allocated = 0; @@ -548,6 +1062,76 @@ static void zend_opcache_static_cache_unlock_entry_stripe(zend_opcache_static_ca fcntl(storage->lock_file, F_SETLK, &mem_unlock); } +#ifdef ZTS + if (storage->entry_locks_initialized && storage->entry_locks[stripe] != NULL) { + tsrm_mutex_unlock(storage->entry_locks[stripe]); + } +#endif +} +#else +static bool zend_opcache_static_cache_lock_entry_stripe_ex(zend_opcache_static_cache_context *context, uint32_t stripe, bool blocking) +{ + zend_opcache_static_cache_storage *storage = &context->storage; + uint32_t *counts = zend_opcache_static_cache_entry_lock_counts_for_context(context); + + if (counts[stripe] != 0) { + counts[stripe]++; + + return true; + } + +#ifdef ZTS + if (!storage->entry_locks_initialized && !zend_opcache_static_cache_entry_locks_startup(storage)) { + return false; + } + + if (storage->entry_locks[stripe] == NULL) { + return false; + } + + if (blocking) { + if (tsrm_mutex_lock(storage->entry_locks[stripe]) != 0) { + return false; + } + } else if (!TryEnterCriticalSection(storage->entry_locks[stripe])) { + return false; + } +#endif + + if (!zend_opcache_static_cache_win32_lock_range(storage, stripe + 1, true, blocking)) { +#ifdef ZTS + tsrm_mutex_unlock(storage->entry_locks[stripe]); +#endif + return false; + } + + counts[stripe] = 1; + + return true; +} + +static bool zend_opcache_static_cache_lock_entry_stripe(zend_opcache_static_cache_context *context, uint32_t stripe) +{ + return zend_opcache_static_cache_lock_entry_stripe_ex(context, stripe, true); +} + +static bool zend_opcache_static_cache_try_lock_entry_stripe(zend_opcache_static_cache_context *context, uint32_t stripe) +{ + return zend_opcache_static_cache_lock_entry_stripe_ex(context, stripe, false); +} + +static void zend_opcache_static_cache_unlock_entry_stripe(zend_opcache_static_cache_context *context, uint32_t stripe) +{ + zend_opcache_static_cache_storage *storage = &context->storage; + uint32_t *counts = zend_opcache_static_cache_entry_lock_counts_for_context(context); + + ZEND_ASSERT(counts[stripe] != 0); + if (--counts[stripe] != 0) { + return; + } + + zend_opcache_static_cache_win32_unlock_range(storage, stripe + 1); + #ifdef ZTS if (storage->entry_locks_initialized && storage->entry_locks[stripe] != NULL) { tsrm_mutex_unlock(storage->entry_locks[stripe]); @@ -564,7 +1148,9 @@ static void zend_opcache_static_cache_entry_lock_dtor(zval *lock_zv) return; } -#ifndef ZEND_WIN32 +#ifdef ZEND_WIN32 + zend_opcache_static_cache_unlock_entry_stripe(lock->context, lock->stripe); +#else if (lock->owner_pid == (zend_ulong) getpid()) { zend_opcache_static_cache_unlock_entry_stripe(lock->context, lock->stripe); } @@ -1001,9 +1587,7 @@ void zend_opcache_static_cache_reset_storage(void) zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; memset(storage, 0, sizeof(*storage)); -#ifndef ZEND_WIN32 storage->lock_file = -1; -#endif } bool zend_opcache_static_cache_header_init_locked(void) @@ -1420,17 +2004,11 @@ bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key) return true; } -#ifndef ZEND_WIN32 if (!zend_opcache_static_cache_lock_entry_stripe(context, stripe)) { zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); return false; } -#else - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); - - return false; -#endif lock = emalloc(sizeof(zend_opcache_static_cache_entry_lock)); lock->context = context; @@ -1443,9 +2021,7 @@ bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key) lock->stripe = stripe; if (zend_hash_add_ptr(zend_opcache_static_cache_entry_locks(context), key, lock) == NULL) { -#ifndef ZEND_WIN32 zend_opcache_static_cache_unlock_entry_stripe(context, stripe); -#endif efree(lock); return true; @@ -1467,13 +2043,9 @@ bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key) return true; } -#ifndef ZEND_WIN32 if (!zend_opcache_static_cache_try_lock_entry_stripe(context, stripe)) { return false; } -#else - return false; -#endif lock = emalloc(sizeof(zend_opcache_static_cache_entry_lock)); lock->context = context; @@ -1486,9 +2058,7 @@ bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key) lock->stripe = stripe; if (zend_hash_add_ptr(zend_opcache_static_cache_entry_locks(context), key, lock) == NULL) { -#ifndef ZEND_WIN32 zend_opcache_static_cache_unlock_entry_stripe(context, stripe); -#endif efree(lock); return true; From 672e150e333a7a90902c6316c7b1fc42910f146c Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 02:05:33 +0000 Subject: [PATCH 04/29] fix: re fix windows build --- ext/opcache/zend_static_cache_storage.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 32d2037b61e1..4b824c25a0b8 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -651,15 +651,25 @@ static void zend_opcache_static_cache_unlock_impl(void) #else static bool zend_opcache_static_cache_win32_open_lock_file_at(zend_opcache_static_cache_storage *storage, const char *directory, const char *base_name) { + size_t directory_len; + const char *separator; + if (directory == NULL || directory[0] == '\0') { return false; } + directory_len = strlen(directory); + separator = directory[directory_len - 1] == '/' || directory[directory_len - 1] == '\\' + ? "" + : "/" + ; + snprintf( storage->lockfile_name, sizeof(storage->lockfile_name), - "%s/%s.lock", + "%s%s%s.lock", directory, + separator, base_name ); @@ -681,10 +691,6 @@ static bool zend_opcache_static_cache_win32_open_lock_file(zend_opcache_static_c storage->size ); - if (zend_opcache_static_cache_win32_open_lock_file_at(storage, ZCG(accel_directives).lockfile_path, base_name)) { - return true; - } - temp_path_len = GetTempPathA(sizeof(temp_path), temp_path); if (temp_path_len == 0 || temp_path_len >= sizeof(temp_path)) { return false; From f665cd7bb9e50ed46c4d323f6d1f4af483897b55 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 02:05:46 +0000 Subject: [PATCH 05/29] fix: regenerate func info --- Zend/Optimizer/zend_func_infos.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index b7b118c710c5..11a115fcdb5c 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -288,6 +288,10 @@ static const func_info_t func_infos[] = { F1("mysqli_use_result", MAY_BE_OBJECT|MAY_BE_FALSE), F1("opcache_get_status", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), F1("opcache_get_configuration", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), + FN("OPcache\\volatile_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), + FN("OPcache\\volatile_cache_info", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY), + FN("OPcache\\persistent_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), + FN("OPcache\\persistent_cache_info", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY), F1("openssl_x509_parse", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_csr_get_subject", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_pkey_get_details", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), From ff14ec8b8e3e5dc7a40f035ef73225e9c614ac6f Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 04:21:08 +0000 Subject: [PATCH 06/29] fix: add test CONFLICTS --- ext/opcache/tests/fpm/CONFLICTS | 1 + 1 file changed, 1 insertion(+) create mode 100644 ext/opcache/tests/fpm/CONFLICTS diff --git a/ext/opcache/tests/fpm/CONFLICTS b/ext/opcache/tests/fpm/CONFLICTS new file mode 100644 index 000000000000..14c2e0b65abd --- /dev/null +++ b/ext/opcache/tests/fpm/CONFLICTS @@ -0,0 +1 @@ +fpm From d81f5a49edb35e4e0614ecd7491b90a6de0e3ced Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 04:23:51 +0000 Subject: [PATCH 07/29] chore: revert unnecessary changes --- ext/date/php_date.c | 142 +++++++------- ...c_internal_object_method_mutation_001.phpt | 123 ++++++++++++ ext/opcache/zend_opcache_serializer.h | 11 +- ext/opcache/zend_static_cache.c | 176 ++++++++++++++++-- ext/opcache/zend_static_cache.h | 8 + ext/opcache/zend_static_cache_internal.h | 1 + ext/spl/spl_array.c | 6 +- ext/spl/spl_fixedarray.c | 6 +- ext/spl/spl_fixedarray.h | 2 - 9 files changed, 385 insertions(+), 90 deletions(-) create mode 100644 ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_001.phpt diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 18ebabeadc47..3d24f0d2a8fa 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -2150,8 +2150,54 @@ static zend_object *date_object_clone_interval(zend_object *this_ptr) /* {{{ */ return &new_obj->std; } /* }}} */ -static bool php_date_initialize_from_hash(php_date_obj *dateobj, const HashTable *myht); -static bool php_date_timezone_initialize_from_hash(php_timezone_obj *tzobj, const HashTable *myht); +static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n) /* {{{ */ +{ + + *table = NULL; + *n = 0; + return zend_std_get_properties(object); +} /* }}} */ + +static void date_interval_object_to_hash(php_interval_obj *intervalobj, HashTable *props) +{ + zval zv; + + /* Records whether this is a special relative interval that needs to be recreated from a string */ + if (intervalobj->from_string) { + ZVAL_BOOL(&zv, (bool)intervalobj->from_string); + zend_hash_str_update(props, "from_string", strlen("from_string"), &zv); + ZVAL_STR_COPY(&zv, intervalobj->date_string); + zend_hash_str_update(props, "date_string", strlen("date_string"), &zv); + return; + } + +#define PHP_DATE_INTERVAL_ADD_PROPERTY(n,f) \ + ZVAL_LONG(&zv, (zend_long)intervalobj->diff->f); \ + zend_hash_str_update(props, n, sizeof(n)-1, &zv); + + PHP_DATE_INTERVAL_ADD_PROPERTY("y", y); + PHP_DATE_INTERVAL_ADD_PROPERTY("m", m); + PHP_DATE_INTERVAL_ADD_PROPERTY("d", d); + PHP_DATE_INTERVAL_ADD_PROPERTY("h", h); + PHP_DATE_INTERVAL_ADD_PROPERTY("i", i); + PHP_DATE_INTERVAL_ADD_PROPERTY("s", s); + ZVAL_DOUBLE(&zv, (double)intervalobj->diff->us / 1000000.0); + zend_hash_str_update(props, "f", sizeof("f") - 1, &zv); + PHP_DATE_INTERVAL_ADD_PROPERTY("invert", invert); + if (intervalobj->diff->days != TIMELIB_UNSET) { + PHP_DATE_INTERVAL_ADD_PROPERTY("days", days); + } else { + ZVAL_FALSE(&zv); + zend_hash_str_update(props, "days", sizeof("days")-1, &zv); + } + ZVAL_BOOL(&zv, (bool)intervalobj->from_string); + zend_hash_str_update(props, "from_string", strlen("from_string"), &zv); + +#undef PHP_DATE_INTERVAL_ADD_PROPERTY +} + +static bool php_date_initialize_from_hash(php_date_obj **dateobj, const HashTable *myht); +static bool php_date_timezone_initialize_from_hash(php_timezone_obj **tzobj, const HashTable *myht); static void php_date_interval_initialize_from_hash(php_interval_obj *intobj, const HashTable *myht); static bool php_date_copy_direct_cache_state( @@ -2228,52 +2274,6 @@ static bool php_date_copy_direct_cache_state( return false; } -static HashTable *date_object_get_gc_interval(zend_object *object, zval **table, int *n) /* {{{ */ -{ - - *table = NULL; - *n = 0; - return zend_std_get_properties(object); -} /* }}} */ - -static void date_interval_object_to_hash(php_interval_obj *intervalobj, HashTable *props) -{ - zval zv; - - /* Records whether this is a special relative interval that needs to be recreated from a string */ - if (intervalobj->from_string) { - ZVAL_BOOL(&zv, (bool)intervalobj->from_string); - zend_hash_str_update(props, "from_string", strlen("from_string"), &zv); - ZVAL_STR_COPY(&zv, intervalobj->date_string); - zend_hash_str_update(props, "date_string", strlen("date_string"), &zv); - return; - } - -#define PHP_DATE_INTERVAL_ADD_PROPERTY(n,f) \ - ZVAL_LONG(&zv, (zend_long)intervalobj->diff->f); \ - zend_hash_str_update(props, n, sizeof(n)-1, &zv); - - PHP_DATE_INTERVAL_ADD_PROPERTY("y", y); - PHP_DATE_INTERVAL_ADD_PROPERTY("m", m); - PHP_DATE_INTERVAL_ADD_PROPERTY("d", d); - PHP_DATE_INTERVAL_ADD_PROPERTY("h", h); - PHP_DATE_INTERVAL_ADD_PROPERTY("i", i); - PHP_DATE_INTERVAL_ADD_PROPERTY("s", s); - ZVAL_DOUBLE(&zv, (double)intervalobj->diff->us / 1000000.0); - zend_hash_str_update(props, "f", sizeof("f") - 1, &zv); - PHP_DATE_INTERVAL_ADD_PROPERTY("invert", invert); - if (intervalobj->diff->days != TIMELIB_UNSET) { - PHP_DATE_INTERVAL_ADD_PROPERTY("days", days); - } else { - ZVAL_FALSE(&zv); - zend_hash_str_update(props, "days", sizeof("days")-1, &zv); - } - ZVAL_BOOL(&zv, (bool)intervalobj->from_string); - zend_hash_str_update(props, "from_string", strlen("from_string"), &zv); - -#undef PHP_DATE_INTERVAL_ADD_PROPERTY -} - static void php_date_direct_cache_add_assoc_int64(zval *array_zv, const char *name, int64_t value) { #if PHP_DATE_SIZEOF_LONG == 8 @@ -2373,11 +2373,15 @@ static bool php_date_unserialize_direct_cache_state(zval *object, zval *state) if (instanceof_function(Z_OBJCE_P(object), date_ce_date) || instanceof_function(Z_OBJCE_P(object), date_ce_immutable)) { - return php_date_initialize_from_hash(Z_PHPDATE_P(object), Z_ARRVAL_P(state)); + php_date_obj *dateobj = Z_PHPDATE_P(object); + + return php_date_initialize_from_hash(&dateobj, Z_ARRVAL_P(state)); } if (instanceof_function(Z_OBJCE_P(object), date_ce_timezone)) { - return php_date_timezone_initialize_from_hash(Z_PHPTIMEZONE_P(object), Z_ARRVAL_P(state)); + php_timezone_obj *tzobj = Z_PHPTIMEZONE_P(object); + + return php_date_timezone_initialize_from_hash(&tzobj, Z_ARRVAL_P(state)); } if (instanceof_function(Z_OBJCE_P(object), date_ce_interval)) { @@ -2393,6 +2397,7 @@ const zend_opcache_static_cache_safe_direct_handlers *php_date_get_direct_cache_ { static const zend_opcache_static_cache_safe_direct_handlers handlers = { true, + { false, NULL, NULL }, php_date_copy_direct_cache_state, NULL, php_date_serialize_direct_cache_state, @@ -3032,7 +3037,7 @@ PHP_METHOD(DateTimeImmutable, createFromTimestamp) } /* }}} */ -static bool php_date_initialize_from_hash(php_date_obj *dateobj, const HashTable *myht) +static bool php_date_initialize_from_hash(php_date_obj **dateobj, const HashTable *myht) { zval *z_date; zval *z_timezone_type; @@ -3061,7 +3066,7 @@ static bool php_date_initialize_from_hash(php_date_obj *dateobj, const HashTable zend_string *tmp = zend_string_concat3( Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), " ", 1, Z_STRVAL_P(z_timezone), Z_STRLEN_P(z_timezone)); - bool ret = php_date_initialize(dateobj, ZSTR_VAL(tmp), ZSTR_LEN(tmp), NULL, NULL, 0); + bool ret = php_date_initialize(*dateobj, ZSTR_VAL(tmp), ZSTR_LEN(tmp), NULL, NULL, 0); zend_string_release(tmp); return ret; } @@ -3081,7 +3086,7 @@ static bool php_date_initialize_from_hash(php_date_obj *dateobj, const HashTable tzobj->tzi.tz = tzi; tzobj->initialized = true; - ret = php_date_initialize(dateobj, Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), NULL, &tmp_obj, 0); + ret = php_date_initialize(*dateobj, Z_STRVAL_P(z_date), Z_STRLEN_P(z_date), NULL, &tmp_obj, 0); zval_ptr_dtor(&tmp_obj); return ret; } @@ -3104,7 +3109,7 @@ PHP_METHOD(DateTime, __set_state) php_date_instantiate(date_ce_date, return_value); dateobj = Z_PHPDATE_P(return_value); - if (!php_date_initialize_from_hash(dateobj, myht)) { + if (!php_date_initialize_from_hash(&dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTime object"); RETURN_THROWS(); } @@ -3126,7 +3131,7 @@ PHP_METHOD(DateTimeImmutable, __set_state) php_date_instantiate(date_ce_immutable, return_value); dateobj = Z_PHPDATE_P(return_value); - if (!php_date_initialize_from_hash(dateobj, myht)) { + if (!php_date_initialize_from_hash(&dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object"); RETURN_THROWS(); } @@ -3211,7 +3216,7 @@ PHP_METHOD(DateTime, __unserialize) dateobj = Z_PHPDATE_P(object); - if (!php_date_initialize_from_hash(dateobj, myht)) { + if (!php_date_initialize_from_hash(&dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTime object"); RETURN_THROWS(); } @@ -3233,7 +3238,7 @@ PHP_METHOD(DateTimeImmutable, __unserialize) dateobj = Z_PHPDATE_P(object); - if (!php_date_initialize_from_hash(dateobj, myht)) { + if (!php_date_initialize_from_hash(&dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object"); RETURN_THROWS(); } @@ -3256,7 +3261,7 @@ static void php_do_date_time_wakeup(INTERNAL_FUNCTION_PARAMETERS, const char *cl myht = Z_OBJPROP_P(object); - if (!php_date_initialize_from_hash(dateobj, myht)) { + if (!php_date_initialize_from_hash(&dateobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for %s object", class_name); RETURN_THROWS(); } @@ -4277,15 +4282,16 @@ PHP_METHOD(DateTimeZone, __construct) } /* }}} */ -static bool php_date_timezone_initialize_from_hash(php_timezone_obj *tzobj, const HashTable *myht) /* {{{ */ +static bool php_date_timezone_initialize_from_hash(php_timezone_obj **tzobj, const HashTable *myht) /* {{{ */ { - zval *z_timezone_type; - zval *z_timezone; + zval *z_timezone_type; if ((z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type") - 1)) == NULL) { return false; } + zval *z_timezone; + if ((z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone") - 1)) == NULL) { return false; } @@ -4302,7 +4308,7 @@ static bool php_date_timezone_initialize_from_hash(php_timezone_obj *tzobj, cons if (UNEXPECTED(zend_str_has_nul_byte(Z_STR_P(z_timezone)))) { return false; } - return timezone_initialize(tzobj, Z_STR_P(z_timezone), NULL); + return timezone_initialize(*tzobj, Z_STR_P(z_timezone), NULL); } /* }}} */ /* {{{ */ @@ -4317,7 +4323,7 @@ PHP_METHOD(DateTimeZone, __set_state) php_date_instantiate(date_ce_timezone, return_value); tzobj = Z_PHPTIMEZONE_P(return_value); - if (!php_date_timezone_initialize_from_hash(tzobj, myht)) { + if (!php_date_timezone_initialize_from_hash(&tzobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object"); RETURN_THROWS(); } @@ -4337,7 +4343,7 @@ PHP_METHOD(DateTimeZone, __wakeup) myht = Z_OBJPROP_P(object); - if (!php_date_timezone_initialize_from_hash(tzobj, myht)) { + if (!php_date_timezone_initialize_from_hash(&tzobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object"); RETURN_THROWS(); } @@ -4401,7 +4407,7 @@ PHP_METHOD(DateTimeZone, __unserialize) tzobj = Z_PHPTIMEZONE_P(object); - if (!php_date_timezone_initialize_from_hash(tzobj, myht)) { + if (!php_date_timezone_initialize_from_hash(&tzobj, myht)) { zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object"); RETURN_THROWS(); } @@ -4828,10 +4834,10 @@ static void php_date_interval_initialize_from_hash(php_interval_obj *intobj, con { /* If we have a date_string, use that instead */ const zval *date_str = zend_hash_str_find(myht, "date_string", strlen("date_string")); - timelib_time *time; - timelib_error_container *err; if (date_str && Z_TYPE_P(date_str) == IS_STRING) { - err = NULL; + timelib_time *time; + timelib_error_container *err = NULL; + time = timelib_strtotime(Z_STRVAL_P(date_str), Z_STRLEN_P(date_str), &err, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper); if (err->error_count > 0) { diff --git a/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_001.phpt b/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_001.phpt new file mode 100644 index 000000000000..a146edabeb0a --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_001.phpt @@ -0,0 +1,123 @@ +--TEST-- +OPcache VolatileStatic tracks internal object method mutations +--EXTENSIONS-- +opcache +spl +--CONFLICTS-- +server +--FILE-- + 0]); + return $value; + } +} + +class VolatileStaticFixedArrayMethodState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function value(): SplFixedArray + { + static $value = null; + + $value ??= SplFixedArray::fromArray(['seed']); + return $value; + } +} + +$action = $_GET['action'] ?? 'read'; +$state = $_GET['state'] ?? 'date'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($state === 'date') { + $value = VolatileStaticDateMethodState::value(); + if ($action === 'mutate') { + $value->modify('+1 day'); + } + echo "date=", $value->format('Y-m-d'), "\n"; + return; +} + +if ($state === 'array') { + $value = VolatileStaticArrayObjectMethodState::value(); + if ($action === 'mutate') { + $value->offsetSet('count', $value->offsetGet('count') + 1); + $value->append('tail'); + } + echo "array=", $value->offsetGet('count'), ",", count($value), "\n"; + return; +} + +$value = VolatileStaticFixedArrayMethodState::value(); +if ($action === 'mutate') { + $value->offsetSet(0, 'changed'); + $value->setSize(2); + $value->offsetSet(1, 'tail'); +} +$second = $value->count() > 1 ? $value->offsetGet(1) : 'none'; +echo "fixed=", $value->count(), ",", $value->offsetGet(0), ",", $second, "\n"; +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_internal_object_method_mutation_001.php'; +foreach (['date', 'array', 'fixed'] as $state) { + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?state=' . $state . '&action=read'); + echo file_get_contents($base . '?state=' . $state . '&action=mutate'); + echo file_get_contents($base . '?state=' . $state . '&action=read'); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +date=2026-01-01 +date=2026-01-02 +date=2026-01-02 +reset +array=0,1 +array=1,2 +array=1,2 +reset +fixed=1,seed,none +fixed=2,changed,tail +fixed=2,changed,tail diff --git a/ext/opcache/zend_opcache_serializer.h b/ext/opcache/zend_opcache_serializer.h index d7467b0b37d6..2b05285f3751 100644 --- a/ext/opcache/zend_opcache_serializer.h +++ b/ext/opcache/zend_opcache_serializer.h @@ -761,7 +761,8 @@ static zend_always_inline bool zend_opcache_serializer_encode_safe_direct_regist { zend_opcache_static_cache_safe_direct_state_serialize_func_t serialize_func; zval state_zv; - HashTable *props; + HashTable *props, empty_props; + bool ok; serialize_func = zend_opcache_static_cache_safe_direct_state_serialize_func(Z_OBJCE_P(zv)); if (serialize_func == NULL) { @@ -777,6 +778,14 @@ static zend_always_inline bool zend_opcache_serializer_encode_safe_direct_regist return false; } + if (zend_opcache_static_cache_safe_direct_state_includes_properties(Z_OBJCE_P(zv))) { + zend_hash_init(&empty_props, 0, NULL, NULL, 0); + ok = zend_opcache_serializer_encode_safe_direct_state_payload(wb, &state_zv, &empty_props); + zend_hash_destroy(&empty_props); + + return ok; + } + props = zend_std_get_properties(Z_OBJ_P(zv)); return zend_opcache_serializer_encode_safe_direct_state_payload(wb, &state_zv, props); diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index e3e14a3daac2..280ba86d47ae 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -321,6 +321,115 @@ static const zend_opcache_static_cache_safe_direct_handlers *zend_opcache_static return NULL; } +static bool zend_opcache_static_cache_safe_direct_serializer_path_serialize( + const zval *object, + zval *state) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = + zend_opcache_static_cache_safe_direct_find_handlers(Z_OBJCE_P(object), NULL) + ; + + ZVAL_UNDEF(state); + if (handlers == NULL || handlers->serializer_path.serialize == NULL) { + return false; + } + + zend_call_known_instance_method_with_0_params(handlers->serializer_path.serialize, Z_OBJ_P(object), state); + if (EG(exception) || Z_TYPE_P(state) != IS_ARRAY) { + if (Z_TYPE_P(state) != IS_UNDEF) { + zval_ptr_dtor(state); + ZVAL_UNDEF(state); + } + + return false; + } + + return true; +} + +static bool zend_opcache_static_cache_safe_direct_serializer_path_unserialize( + zval *object, + zval *state) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = + zend_opcache_static_cache_safe_direct_find_handlers(Z_OBJCE_P(object), NULL) + ; + + if (handlers == NULL || handlers->serializer_path.unserialize == NULL || Z_TYPE_P(state) != IS_ARRAY) { + return false; + } + + zend_call_known_instance_method_with_1_params(handlers->serializer_path.unserialize, Z_OBJ_P(object), NULL, state); + + return !EG(exception); +} + +static bool zend_opcache_static_cache_safe_direct_serializer_path_copy( + void *context, + zend_object *old_object, + zend_object *new_object, + zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value) +{ + zval old_zv, new_zv, state_zv, cloned_state_zv; + bool result = false; + + if (clone_value == NULL) { + return false; + } + + ZVAL_OBJ(&old_zv, old_object); + ZVAL_OBJ(&new_zv, new_object); + ZVAL_UNDEF(&state_zv); + ZVAL_UNDEF(&cloned_state_zv); + + if (!zend_opcache_static_cache_safe_direct_serializer_path_serialize(&old_zv, &state_zv)) { + goto cleanup; + } + + if (!clone_value(context, &cloned_state_zv, &state_zv) || + Z_TYPE(cloned_state_zv) != IS_ARRAY) { + goto cleanup; + } + + result = zend_opcache_static_cache_safe_direct_serializer_path_unserialize( + &new_zv, + &cloned_state_zv + ); + +cleanup: + if (Z_TYPE(cloned_state_zv) != IS_UNDEF) { + zval_ptr_dtor(&cloned_state_zv); + } + if (Z_TYPE(state_zv) != IS_UNDEF) { + zval_ptr_dtor(&state_zv); + } + + return result && !EG(exception); +} + +static bool zend_opcache_static_cache_safe_direct_serializer_path_has_unstorable( + void *context, + const zval *value, + zend_opcache_static_cache_safe_direct_value_has_unstorable_func_t value_has_unstorable) +{ + zval state_zv; + bool result; + + if (value_has_unstorable == NULL) { + return false; + } + + ZVAL_UNDEF(&state_zv); + if (!zend_opcache_static_cache_safe_direct_serializer_path_serialize(value, &state_zv)) { + return true; + } + + result = value_has_unstorable(context, &state_zv); + zval_ptr_dtor(&state_zv); + + return result; +} + zend_opcache_static_cache_safe_direct_state_copy_func_t zend_opcache_static_cache_safe_direct_copy_func( zend_class_entry *ce, zend_class_entry **base_ce_ptr) @@ -366,6 +475,14 @@ bool zend_opcache_static_cache_safe_direct_allows_custom_serializers(zend_class_ return handlers != NULL && handlers->allows_custom_serializers; } +bool zend_opcache_static_cache_safe_direct_state_includes_properties(zend_class_entry *ce) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = + zend_opcache_static_cache_safe_direct_find_handlers(ce, NULL); + + return handlers != NULL && handlers->serializer_path.state_includes_properties; +} + static zend_always_inline zend_string *zend_opcache_static_cache_validate_volatile_static_attribute( zend_attribute *attr, uint32_t target ZEND_ATTRIBUTE_UNUSED, @@ -417,6 +534,28 @@ static zend_always_inline void zend_opcache_static_cache_register_classes(void) zend_opcache_static_cache_exception_ce = register_class_OPcache_StaticCacheException(zend_ce_exception); } +static zend_always_inline void zend_opcache_static_cache_safe_direct_register_serializer_class( + zend_class_entry *ce, + bool allows_custom_serializers) +{ + zend_opcache_static_cache_safe_direct_handlers handlers; + + if (ce == NULL || ce->__serialize == NULL || ce->__unserialize == NULL) { + return; + } + + handlers.allows_custom_serializers = allows_custom_serializers; + handlers.serializer_path.state_includes_properties = true; + handlers.serializer_path.serialize = ce->__serialize; + handlers.serializer_path.unserialize = ce->__unserialize; + handlers.copy = zend_opcache_static_cache_safe_direct_serializer_path_copy; + handlers.state_has_unstorable = zend_opcache_static_cache_safe_direct_serializer_path_has_unstorable; + handlers.state_serialize = zend_opcache_static_cache_safe_direct_serializer_path_serialize; + handlers.state_unserialize = zend_opcache_static_cache_safe_direct_serializer_path_unserialize; + + zend_opcache_static_cache_safe_direct_register_class(ce, &handlers); +} + static zend_always_inline void zend_opcache_static_cache_safe_direct_register_internal_classes(void) { zend_class_entry *date_ce, *immutable_ce, *timezone_ce, *interval_ce, *fixedarray_ce, *arrayobject_ce, *arrayiterator_ce, *recursive_arrayiterator_ce; @@ -444,19 +583,34 @@ static zend_always_inline void zend_opcache_static_cache_safe_direct_register_in date_handlers = php_date_get_direct_cache_handlers(); spl_fixedarray_handlers = spl_fixedarray_object_get_direct_cache_handlers(); spl_array_handlers = spl_array_object_get_direct_cache_handlers(); - if (date_handlers == NULL || spl_fixedarray_handlers == NULL || spl_array_handlers == NULL) { - return; + if (date_handlers != NULL) { + zend_opcache_static_cache_safe_direct_register_class(date_ce, date_handlers); + zend_opcache_static_cache_safe_direct_register_class(immutable_ce, date_handlers); + zend_opcache_static_cache_safe_direct_register_class(timezone_ce, date_handlers); + zend_opcache_static_cache_safe_direct_register_class(interval_ce, date_handlers); + } else { + zend_opcache_static_cache_safe_direct_register_serializer_class(date_ce, true); + zend_opcache_static_cache_safe_direct_register_serializer_class(immutable_ce, true); + zend_opcache_static_cache_safe_direct_register_serializer_class(timezone_ce, true); + zend_opcache_static_cache_safe_direct_register_serializer_class(interval_ce, true); + } + + if (spl_fixedarray_handlers != NULL) { + zend_opcache_static_cache_safe_direct_register_class(fixedarray_ce, spl_fixedarray_handlers); + } else { + zend_opcache_static_cache_safe_direct_register_serializer_class(fixedarray_ce, false); + } + + if (spl_array_handlers != NULL) { + zend_opcache_static_cache_safe_direct_register_class(arrayobject_ce, spl_array_handlers); + zend_opcache_static_cache_safe_direct_register_class(arrayiterator_ce, spl_array_handlers); + zend_opcache_static_cache_safe_direct_register_class(recursive_arrayiterator_ce, spl_array_handlers); + } else { + zend_opcache_static_cache_safe_direct_register_serializer_class(arrayobject_ce, false); + zend_opcache_static_cache_safe_direct_register_serializer_class(arrayiterator_ce, false); + zend_opcache_static_cache_safe_direct_register_serializer_class(recursive_arrayiterator_ce, false); } - zend_opcache_static_cache_safe_direct_register_class(date_ce, date_handlers); - zend_opcache_static_cache_safe_direct_register_class(immutable_ce, date_handlers); - zend_opcache_static_cache_safe_direct_register_class(timezone_ce, date_handlers); - zend_opcache_static_cache_safe_direct_register_class(interval_ce, date_handlers); - zend_opcache_static_cache_safe_direct_register_class(fixedarray_ce, spl_fixedarray_handlers); - zend_opcache_static_cache_safe_direct_register_class(arrayobject_ce, spl_array_handlers); - zend_opcache_static_cache_safe_direct_register_class(arrayiterator_ce, spl_array_handlers); - zend_opcache_static_cache_safe_direct_register_class(recursive_arrayiterator_ce, spl_array_handlers); - zend_opcache_static_cache_safe_direct_classes_marked = true; } diff --git a/ext/opcache/zend_static_cache.h b/ext/opcache/zend_static_cache.h index d50f5c57e7ca..9d748a64ec95 100644 --- a/ext/opcache/zend_static_cache.h +++ b/ext/opcache/zend_static_cache.h @@ -55,13 +55,21 @@ typedef bool (*zend_opcache_static_cache_safe_direct_state_unserialize_func_t)( #ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD # define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD +typedef struct _zend_opcache_static_cache_safe_direct_serializer_path zend_opcache_static_cache_safe_direct_serializer_path; typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; #endif #ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_DEFINED # define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_DEFINED +struct _zend_opcache_static_cache_safe_direct_serializer_path { + bool state_includes_properties; + zend_function *serialize; + zend_function *unserialize; +}; + struct _zend_opcache_static_cache_safe_direct_handlers { bool allows_custom_serializers; + zend_opcache_static_cache_safe_direct_serializer_path serializer_path; zend_opcache_static_cache_safe_direct_state_copy_func_t copy; zend_opcache_static_cache_safe_direct_state_has_unstorable_func_t state_has_unstorable; zend_opcache_static_cache_safe_direct_state_serialize_func_t state_serialize; diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index 36809e463f1f..0456aaf0fb23 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -443,6 +443,7 @@ zend_opcache_static_cache_safe_direct_state_serialize_func_t zend_opcache_static zend_opcache_static_cache_safe_direct_state_unserialize_func_t zend_opcache_static_cache_safe_direct_state_unserialize_func( zend_class_entry *ce); bool zend_opcache_static_cache_safe_direct_allows_custom_serializers(zend_class_entry *ce); +bool zend_opcache_static_cache_safe_direct_state_includes_properties(zend_class_entry *ce); bool zend_opcache_static_cache_calculate_shared_graph_size(const zval *value, size_t *buffer_len); bool zend_opcache_static_cache_build_shared_graph_in_place(const zval *value, unsigned char *buffer, size_t buffer_len, size_t *graph_len); bool zend_opcache_static_cache_shared_graph_copy_fits_buffer( diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 37c3c26858f1..21cf72832d55 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -19,6 +19,7 @@ #include "php.h" #include "ext/standard/php_var.h" #include "zend_smart_str.h" +#include "ext/opcache/zend_static_cache.h" #include "zend_execute.h" #include "zend_interfaces.h" #include "zend_exceptions.h" @@ -29,8 +30,6 @@ #include "spl_exceptions.h" #include "spl_functions.h" /* For spl_set_private_debug_info_property() */ -#include "ext/opcache/zend_static_cache.h" /* for zend_opcache_static_cache_safe_direct_handlers */ - /* Defined later in the file */ PHPAPI zend_class_entry *spl_ce_ArrayIterator; PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator; @@ -1544,8 +1543,6 @@ static bool spl_array_object_copy_direct_cache_state( goto cleanup; } - /* Properties are restored by the caller after all internal state zvals - * have been copied through the shared graph clone context. */ array_init(&empty_props_zv); zend_hash_index_update(Z_ARRVAL(state_zv), 2, &empty_props_zv); @@ -1629,6 +1626,7 @@ const zend_opcache_static_cache_safe_direct_handlers *spl_array_object_get_direc { static const zend_opcache_static_cache_safe_direct_handlers handlers = { false, + { false, NULL, NULL }, spl_array_object_copy_direct_cache_state, spl_array_object_direct_cache_state_has_unstorable, spl_array_object_serialize_direct_cache_state, diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index 5a33171d508b..13ccd25093e9 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -18,6 +18,7 @@ #endif #include "php.h" +#include "ext/opcache/zend_static_cache.h" #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_attributes.h" @@ -28,8 +29,6 @@ #include "spl_exceptions.h" #include "ext/json/php_json.h" /* For php_json_serializable_ce */ -#include "ext/opcache/zend_static_cache.h" /* for zend_opcache_static_cache_safe_direct_handlers */ - static zend_object_handlers spl_handler_SplFixedArray; PHPAPI zend_class_entry *spl_ce_SplFixedArray; @@ -700,8 +699,6 @@ static bool spl_fixedarray_object_copy_direct_cache_state( goto cleanup; } - /* SplFixedArray serializes elements and object properties into the same - * array. Properties are restored by the caller. */ array_init_size(&elements_zv, zend_hash_num_elements(Z_ARRVAL(state_zv))); ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL(state_zv), key, elem) { if (key == NULL) { @@ -801,6 +798,7 @@ const zend_opcache_static_cache_safe_direct_handlers *spl_fixedarray_object_get_ { static const zend_opcache_static_cache_safe_direct_handlers handlers = { false, + { false, NULL, NULL }, spl_fixedarray_object_copy_direct_cache_state, spl_fixedarray_object_direct_cache_state_has_unstorable, spl_fixedarray_object_serialize_direct_cache_state, diff --git a/ext/spl/spl_fixedarray.h b/ext/spl/spl_fixedarray.h index 660a90b8bd11..3ec260070b7b 100644 --- a/ext/spl/spl_fixedarray.h +++ b/ext/spl/spl_fixedarray.h @@ -16,8 +16,6 @@ #ifndef SPL_FIXEDARRAY_H #define SPL_FIXEDARRAY_H -#include "php.h" - extern PHPAPI zend_class_entry *spl_ce_SplFixedArray; #ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD From 1a1348c3d3d57534d434663a3a1fbd9b980b92c5 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 14:50:15 +0000 Subject: [PATCH 08/29] chore: revert unnecessary chang for JIT --- ext/opcache/jit/zend_jit_ir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 0d4b35bc5646..7ffbf655bac3 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -16147,7 +16147,7 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, } else if (fetch_type == BP_VAR_W) { flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS; if (flags && (!known_prop_info || ZEND_TYPE_IS_SET(known_prop_info->type))) { - ir_ref merge = IR_UNUSED; + ir_ref merge = IR_UNUSED; if (!known_prop_info) { // JIT: if (ZEND_TYPE_IS_SET(property_info->type)) From 78595b3da2d656e42be9beb0dd3af8aef67e65ef Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 14:52:38 +0000 Subject: [PATCH 09/29] fix: opcache is currently always bundled --- ext/date/php_date.h | 6 +-- ext/opcache/tests/bug66338.phpt | 1 - ext/opcache/tests/php_cli_server.inc | 70 +++++++++++++++++++++++----- ext/opcache/zend_static_cache.h | 6 --- ext/spl/spl_array.h | 6 +-- ext/spl/spl_fixedarray.h | 6 +-- 6 files changed, 65 insertions(+), 30 deletions(-) diff --git a/ext/date/php_date.h b/ext/date/php_date.h index ffb8072b47c7..66a6706d91e1 100644 --- a/ext/date/php_date.h +++ b/ext/date/php_date.h @@ -18,6 +18,8 @@ #include "lib/timelib.h" #include "Zend/zend_hash.h" +#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ + /* Same as SIZEOF_ZEND_LONG but using TIMELIB_LONG_MAX/MIN */ #if TIMELIB_LONG_MAX == INT32_MAX # define PHP_DATE_SIZEOF_LONG 4 @@ -161,10 +163,6 @@ PHPAPI bool php_date_initialize(php_date_obj *dateobj, const char *time_str, siz PHPAPI void php_date_initialize_from_ts_long(php_date_obj *dateobj, zend_long sec, int usec); PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts); -#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD -# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD -typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; -#endif const zend_opcache_static_cache_safe_direct_handlers *php_date_get_direct_cache_handlers(void); #endif /* PHP_DATE_H */ diff --git a/ext/opcache/tests/bug66338.phpt b/ext/opcache/tests/bug66338.phpt index 61065a8e3fa8..86170308dd3d 100644 --- a/ext/opcache/tests/bug66338.phpt +++ b/ext/opcache/tests/bug66338.phpt @@ -28,7 +28,6 @@ file_put_contents( "$root-clientUK.php", ' STDIN, - 1 => STDOUT, - 2 => array("null"), + 1 => $output_file_fd, + 2 => $output_file_fd, ); $handle = proc_open($cmd, $descriptorspec, $pipes, $doc_root, null, array("suppress_errors" => true)); + if ($handle === false) { + printf("Failed to start server process\nServer output:\n%s\n", file_get_contents($output_file)); + @unlink($output_file); + exit(1); + } + + $bound = null; + for ($i=0; $i < 60; $i++) { + usleep(50000); // 50ms per try + $status = proc_get_status($handle); + // Failure, the server is no longer running + if (!($status && $status['running'])) { + printf("Server failed to start\nServer output:\n%s\n", file_get_contents($output_file)); + proc_terminate($handle); + exit(1); + } + + $output = file_get_contents($output_file); + if (preg_match('@PHP \S* Development Server \(https?://(.*?:\d+)\) started@', $output, $matches)) { + $bound = $matches[1]; + break; + } + } + + if ($bound === null) { + printf("Server did not output startup message\nServer output:\n%s\n", file_get_contents($output_file)); + proc_terminate($handle); + exit(1); + } + + $port = (int) substr($bound, strrpos($bound, ':') + 1); + define ("PHP_CLI_SERVER_PORT", $port); + define ("PHP_CLI_SERVER_ADDRESS", PHP_CLI_SERVER_HOSTNAME.":".PHP_CLI_SERVER_PORT); - // note: even when server prints 'Listening on localhost:8964...Press Ctrl-C to quit.' - // it might not be listening yet...need to wait until fsockopen() call returns + // note: even when server prints its startup message it might not be listening yet; + // wait until fsockopen() succeeds. $error = "Unable to connect to server\n"; for ($i=0; $i < 60; $i++) { usleep(50000); // 50ms per try $status = proc_get_status($handle); - $fp = @fsockopen(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT); + $fp = @fsockopen("tcp://" . $bound); // Failure, the server is no longer running if (!($status && $status['running'])) { - $error = "Server is not running\n"; + $error = sprintf("Server is not running\nServer output:\n%s\n", file_get_contents($output_file)); break; } // Success, Connected to servers @@ -45,22 +86,29 @@ function php_cli_server_start($ini = "") { if ($error) { echo $error; proc_terminate($handle); + @unlink($output_file); exit(1); } register_shutdown_function( - function($handle) { + function($handle, $output_file) { proc_terminate($handle); /* Wait for server to shutdown */ for ($i = 0; $i < 60; $i++) { $status = proc_get_status($handle); if (!($status && $status['running'])) { + if ($status && $status['exitcode'] !== -1 && $status['exitcode'] !== 0) { + printf("Server exited with non-zero status: %d\n", $status['exitcode']); + printf("Server output:\n%s\n", file_get_contents($output_file)); + } break; } usleep(50000); } + @unlink($output_file); }, - $handle + $handle, + $output_file ); } diff --git a/ext/opcache/zend_static_cache.h b/ext/opcache/zend_static_cache.h index 9d748a64ec95..b5ce9458e0bc 100644 --- a/ext/opcache/zend_static_cache.h +++ b/ext/opcache/zend_static_cache.h @@ -53,14 +53,9 @@ typedef bool (*zend_opcache_static_cache_safe_direct_state_unserialize_func_t)( zval *state ); -#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD -# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD typedef struct _zend_opcache_static_cache_safe_direct_serializer_path zend_opcache_static_cache_safe_direct_serializer_path; typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; -#endif -#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_DEFINED -# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_DEFINED struct _zend_opcache_static_cache_safe_direct_serializer_path { bool state_includes_properties; zend_function *serialize; @@ -75,7 +70,6 @@ struct _zend_opcache_static_cache_safe_direct_handlers { zend_opcache_static_cache_safe_direct_state_serialize_func_t state_serialize; zend_opcache_static_cache_safe_direct_state_unserialize_func_t state_unserialize; }; -#endif BEGIN_EXTERN_C() diff --git a/ext/spl/spl_array.h b/ext/spl/spl_array.h index 10ca1b307366..58e994b68ab7 100644 --- a/ext/spl/spl_array.h +++ b/ext/spl/spl_array.h @@ -17,6 +17,8 @@ #include "php.h" +#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ + #define SPL_ARRAY_STD_PROP_LIST 0x00000001 #define SPL_ARRAY_ARRAY_AS_PROPS 0x00000002 #define SPL_ARRAY_CHILD_ARRAYS_ONLY 0x00000004 @@ -29,10 +31,6 @@ extern PHPAPI zend_class_entry *spl_ce_ArrayObject; extern PHPAPI zend_class_entry *spl_ce_ArrayIterator; extern PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator; -#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD -# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD -typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; -#endif const zend_opcache_static_cache_safe_direct_handlers *spl_array_object_get_direct_cache_handlers(void); PHP_MINIT_FUNCTION(spl_array); diff --git a/ext/spl/spl_fixedarray.h b/ext/spl/spl_fixedarray.h index 3ec260070b7b..447c1f9772e4 100644 --- a/ext/spl/spl_fixedarray.h +++ b/ext/spl/spl_fixedarray.h @@ -16,12 +16,10 @@ #ifndef SPL_FIXEDARRAY_H #define SPL_FIXEDARRAY_H +#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ + extern PHPAPI zend_class_entry *spl_ce_SplFixedArray; -#ifndef ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD -# define ZEND_OPCACHE_STATIC_CACHE_SAFE_DIRECT_HANDLERS_FWD -typedef struct _zend_opcache_static_cache_safe_direct_handlers zend_opcache_static_cache_safe_direct_handlers; -#endif const zend_opcache_static_cache_safe_direct_handlers *spl_fixedarray_object_get_direct_cache_handlers(void); PHP_MINIT_FUNCTION(spl_fixedarray); From b78f861f13ea8604e1b5ee662c2d42773cea11cc Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 14:59:29 +0000 Subject: [PATCH 10/29] chore: add licence header to test helpers --- ...explicit_cache_store_delete_zts_threads_001.c | 2 ++ .../helpers/persistent_static_zts_threads_001.c | 16 ++++++++++++++++ .../volatile_cache_process_lock_mode_001.c | 16 ++++++++++++++++ ...olatile_cache_retired_shared_graph_free_001.c | 16 ++++++++++++++++ ...ile_cache_zts_request_shared_graph_refs_001.c | 16 ++++++++++++++++ .../helpers/volatile_cache_zts_threads_001.c | 2 ++ .../helpers/volatile_cache_zts_threads_002.c | 2 ++ .../helpers/volatile_cache_zts_threads_003.c | 2 ++ 8 files changed, 72 insertions(+) diff --git a/ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c b/ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c index a880e2bcac83..911669534797 100644 --- a/ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c +++ b/ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c @@ -10,6 +10,8 @@ | | | SPDX-License-Identifier: BSD-3-Clause | +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H diff --git a/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c b/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c index 0774b0ce6962..8c09757cd348 100644 --- a/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c +++ b/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c @@ -1,3 +1,19 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + #ifdef HAVE_CONFIG_H # include "php_config.h" #endif diff --git a/ext/opcache/tests/helpers/volatile_cache_process_lock_mode_001.c b/ext/opcache/tests/helpers/volatile_cache_process_lock_mode_001.c index ca0ad94e69a7..a267667949f6 100644 --- a/ext/opcache/tests/helpers/volatile_cache_process_lock_mode_001.c +++ b/ext/opcache/tests/helpers/volatile_cache_process_lock_mode_001.c @@ -1,3 +1,19 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + #ifdef HAVE_CONFIG_H # include "php_config.h" # undef HAVE_CONFIG_H diff --git a/ext/opcache/tests/helpers/volatile_cache_retired_shared_graph_free_001.c b/ext/opcache/tests/helpers/volatile_cache_retired_shared_graph_free_001.c index 960045d2b2da..a9d681ebefa0 100644 --- a/ext/opcache/tests/helpers/volatile_cache_retired_shared_graph_free_001.c +++ b/ext/opcache/tests/helpers/volatile_cache_retired_shared_graph_free_001.c @@ -1,3 +1,19 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + #ifdef HAVE_CONFIG_H # include "php_config.h" # undef HAVE_CONFIG_H diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c b/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c index cacde6cfb164..656383cbf0b1 100644 --- a/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c +++ b/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c @@ -1,3 +1,19 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ +*/ + #ifdef HAVE_CONFIG_H # include "php_config.h" # undef HAVE_CONFIG_H diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_001.c b/ext/opcache/tests/helpers/volatile_cache_zts_threads_001.c index 7f7b74edc390..9e09b44537ef 100644 --- a/ext/opcache/tests/helpers/volatile_cache_zts_threads_001.c +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_001.c @@ -10,6 +10,8 @@ | | | SPDX-License-Identifier: BSD-3-Clause | +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_002.c b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002.c index 09ca013893fc..d9b28c8f86e1 100644 --- a/ext/opcache/tests/helpers/volatile_cache_zts_threads_002.c +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_002.c @@ -10,6 +10,8 @@ | | | SPDX-License-Identifier: BSD-3-Clause | +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c index c5b1a800219c..b163e050fcb3 100644 --- a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c @@ -10,6 +10,8 @@ | | | SPDX-License-Identifier: BSD-3-Clause | +----------------------------------------------------------------------+ + | Author: Go Kudo | + +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H From 55bfd9af4c2c0855f596842f69a91764b9dec763 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Sat, 16 May 2026 15:17:18 +0000 Subject: [PATCH 11/29] fix: trying fix to conflicted test failure --- ext/opcache/tests/php_cli_server.inc | 4 ++-- ...tic_internal_object_method_mutation_001.phpt | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ext/opcache/tests/php_cli_server.inc b/ext/opcache/tests/php_cli_server.inc index 0376c812d39e..1324549f4c5d 100644 --- a/ext/opcache/tests/php_cli_server.inc +++ b/ext/opcache/tests/php_cli_server.inc @@ -1,14 +1,14 @@ Date: Sat, 16 May 2026 15:48:39 +0000 Subject: [PATCH 12/29] fix: move mutation test to under fpm tests --- ..._internal_object_method_mutation_001.phpt} | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) rename ext/opcache/tests/{static_cache_volatile_static_internal_object_method_mutation_001.phpt => fpm/static_cache_fpm_volatile_static_internal_object_method_mutation_001.phpt} (62%) diff --git a/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_internal_object_method_mutation_001.phpt similarity index 62% rename from ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_001.phpt rename to ext/opcache/tests/fpm/static_cache_fpm_volatile_static_internal_object_method_mutation_001.phpt index 89796754ecd3..a3016f8a826c 100644 --- a/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_internal_object_method_mutation_001.phpt @@ -1,18 +1,27 @@ --TEST-- -OPcache VolatileStatic tracks internal object method mutations +FPM: OPcache VolatileStatic tracks internal object method mutations --EXTENSIONS-- opcache spl ---CONFLICTS-- -all +--SKIPIF-- + --FILE-- count() > 1 ? $value->offsetGet(1) : 'none'; echo "fixed=", $value->count(), ",", $value->offsetGet(0), ",", $second, "\n"; -PHP); - -$php = getenv('TEST_PHP_EXECUTABLE'); -if ($php) { - $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; - putenv('TEST_PHP_EXECUTABLE=' . $php); +PHP; + +$tester = new FPM\Tester($cfg, $code); +$tester->start(iniEntries: [ + 'opcache.enable' => '1', + 'opcache.static_cache.volatile_size_mb' => '32', + 'opcache.file_update_protection' => '0', + 'opcache.jit' => '0', +]); +$tester->expectLogStartNotices(); + +function static_cache_internal_object_fpm_request(FPM\Tester $tester, string $query): void +{ + echo $tester->request($query)->getBody(), "\n"; } -include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0', $docRoot); - -register_shutdown_function(static function () use ($docRoot, $script) { - @unlink($docRoot . '/' . $script); - @rmdir($docRoot); -}); - -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/' . $script; foreach (['date', 'array', 'fixed'] as $state) { - echo file_get_contents($base . '?action=reset'); - echo file_get_contents($base . '?state=' . $state . '&action=read'); - echo file_get_contents($base . '?state=' . $state . '&action=mutate'); - echo file_get_contents($base . '?state=' . $state . '&action=read'); + static_cache_internal_object_fpm_request($tester, 'action=reset'); + static_cache_internal_object_fpm_request($tester, 'state=' . $state . '&action=read'); + static_cache_internal_object_fpm_request($tester, 'state=' . $state . '&action=mutate'); + static_cache_internal_object_fpm_request($tester, 'state=' . $state . '&action=read'); } -?> ---CLEAN-- -terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); ?> --EXPECT-- reset @@ -130,3 +137,8 @@ reset fixed=1,seed,none fixed=2,changed,tail fixed=2,changed,tail +--CLEAN-- + From e36f4a9b0bc543e9a359df3d435ddf8933e0ee48 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Mon, 18 May 2026 10:56:27 +0000 Subject: [PATCH 13/29] fix: RFC 1.1 --- Zend/Optimizer/zend_func_infos.h | 4 +- ext/opcache/opcache.stub.php | 48 ++- ext/opcache/opcache_arginfo.h | 100 ++++- ...s_blob_dynamic_method_statics_jit_001.phpt | 4 +- ...ent_static_class_blob_persistence_001.phpt | 2 +- ...static_object_assignment_snapshot_001.phpt | 6 +- ...ent_static_property_array_publish_001.phpt | 4 +- ...c_internal_object_method_mutation_001.phpt | 12 +- ...atic_cache_attribute_class_delete_001.phpt | 367 ++++++++++++++++++ ..._cache_attribute_exact_key_delete_001.phpt | 227 +++++++++++ ...che_explicit_cache_key_validation_001.phpt | 99 ++++- ...c_cache_explicit_cache_lock_lease_001.phpt | 89 +++++ ...c_cache_explicit_cache_signatures_001.phpt | 18 +- ...cit_cache_store_array_invalid_key_001.phpt | 4 +- ...tatic_cache_explicit_cache_unlock_001.phpt | 88 +++++ ...ent_cache_atomic_increment_create_001.phpt | 19 +- ...tatic_cache_persistent_cache_info_001.phpt | 2 +- ...static_attribute_scope_separation_001.phpt | 2 +- ...he_persistent_static_capture_miss_001.phpt | 10 +- ...s_blob_dynamic_method_statics_jit_001.phpt | 4 +- ...tent_static_class_blob_single_key_001.phpt | 2 +- ...static_object_assignment_snapshot_001.phpt | 6 +- ...c_property_array_mutation_publish_001.phpt | 2 +- .../static_cache_startup_failure_001.phpt | 24 +- ...cache_volatile_cache_info_surface_001.phpt | 38 +- ...he_volatile_cache_storable_values_001.phpt | 6 +- ...ernal_object_method_mutation_fork_001.phpt | 159 ++++++++ ...ic_cache_volatile_static_strategy_001.phpt | 2 +- .../static_cache_windows_backend_001.phpt | 12 +- ext/opcache/zend_static_cache.c | 200 ++++++++-- ext/opcache/zend_static_cache_entries.c | 5 +- ext/opcache/zend_static_cache_internal.h | 17 +- ext/opcache/zend_static_cache_statics.c | 57 ++- ext/opcache/zend_static_cache_storage.c | 279 +++++++++++-- 34 files changed, 1764 insertions(+), 154 deletions(-) create mode 100644 ext/opcache/tests/static_cache_attribute_class_delete_001.phpt create mode 100644 ext/opcache/tests/static_cache_attribute_exact_key_delete_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_lock_lease_001.phpt create mode 100644 ext/opcache/tests/static_cache_explicit_cache_unlock_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_fork_001.phpt diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 11a115fcdb5c..90cdabf70094 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -289,9 +289,9 @@ static const func_info_t func_infos[] = { F1("opcache_get_status", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), F1("opcache_get_configuration", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), FN("OPcache\\volatile_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), - FN("OPcache\\volatile_cache_info", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY), + FN("OPcache\\volatile_cache_info", MAY_BE_OBJECT), FN("OPcache\\persistent_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), - FN("OPcache\\persistent_cache_info", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY), + FN("OPcache\\persistent_cache_info", MAY_BE_OBJECT), F1("openssl_x509_parse", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_csr_get_subject", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_pkey_get_details", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), diff --git a/ext/opcache/opcache.stub.php b/ext/opcache/opcache.stub.php index 4b793b491b8f..78fc79b56928 100644 --- a/ext/opcache/opcache.stub.php +++ b/ext/opcache/opcache.stub.php @@ -36,6 +36,32 @@ class StaticCacheException extends \Exception { } +/** @strict-properties */ +final readonly class StaticCacheInfo +{ + private function __construct() {} + + public bool $enabled; + + public bool $available; + + public bool $startup_failed; + + public bool $backend_initialized; + + public int $configured_memory; + + public int $shared_memory; + + public int $entry_count; + + public int $segment_count; + + public string $shared_model; + + public ?string $failure_reason; +} + #[\Attribute(13)] /* TARGET_CLASS | TARGET_METHOD | TARGET_PROPERTY */ final class PersistentStatic { @@ -75,18 +101,17 @@ function volatile_fetch_array(array $keys, ?array $default = null): ?array {} function volatile_exists(string $key): bool {} -function volatile_lock(string $key): bool {} +function volatile_lock(string $key, int $lease = 0): bool {} -function volatile_delete(string $key): void {} +function volatile_unlock(string $key): bool {} + +function volatile_delete(string $key_or_class): void {} function volatile_delete_array(array $keys): void {} function volatile_clear(): void {} -/** - * @return array - */ -function volatile_cache_info(): array {} +function volatile_cache_info(): StaticCacheInfo {} function persistent_store(string $key, null|bool|int|float|string|array|object $value): void {} @@ -101,9 +126,11 @@ function persistent_fetch_array(array $keys, ?array $default = null): ?array {} function persistent_exists(string $key): bool {} -function persistent_lock(string $key): bool {} +function persistent_lock(string $key, int $lease = 0): bool {} + +function persistent_unlock(string $key): bool {} -function persistent_delete(string $key): void {} +function persistent_delete(string $key_or_class): void {} function persistent_delete_array(array $keys): void {} @@ -113,9 +140,6 @@ function persistent_atomic_increment(string $key, int $step = 1): int {} function persistent_atomic_decrement(string $key, int $step = 1): int {} -/** - * @return array - */ -function persistent_cache_info(): array {} +function persistent_cache_info(): StaticCacheInfo {} } diff --git a/ext/opcache/opcache_arginfo.h b/ext/opcache/opcache_arginfo.h index 4aa7f5323e33..ca9f9288340a 100644 --- a/ext/opcache/opcache_arginfo.h +++ b/ext/opcache/opcache_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit opcache.stub.php instead. - * Stub hash: 7014b50c5940d238d5a5b3e4902a457f183ee298 */ + * Stub hash: 2ebf95d1f9450cd5600aff1b40d93c8ad89bb108 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_reset, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -53,10 +53,15 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_exists, 0, 1, _ ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_END_ARG_INFO() -#define arginfo_OPcache_volatile_lock arginfo_OPcache_volatile_exists +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_lock, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, lease, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +#define arginfo_OPcache_volatile_unlock arginfo_OPcache_volatile_exists ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_delete, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, key_or_class, IS_STRING, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_delete_array, 0, 1, IS_VOID, 0) @@ -66,7 +71,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_clear, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_cache_info, 0, 0, IS_ARRAY, 0) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_OPcache_volatile_cache_info, 0, 0, OPcache\\StaticCacheInfo, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_persistent_store, 0, 2, IS_VOID, 0) @@ -84,7 +89,9 @@ ZEND_END_ARG_INFO() #define arginfo_OPcache_persistent_exists arginfo_OPcache_volatile_exists -#define arginfo_OPcache_persistent_lock arginfo_OPcache_volatile_exists +#define arginfo_OPcache_persistent_lock arginfo_OPcache_volatile_lock + +#define arginfo_OPcache_persistent_unlock arginfo_OPcache_volatile_unlock #define arginfo_OPcache_persistent_delete arginfo_OPcache_volatile_delete @@ -101,6 +108,9 @@ ZEND_END_ARG_INFO() #define arginfo_OPcache_persistent_cache_info arginfo_OPcache_volatile_cache_info +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_OPcache_StaticCacheInfo___construct, 0, 0, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_OPcache_VolatileStatic___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, ttl, IS_LONG, 0, "0") ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, strategy, OPcache\\CacheStrategy, 0, "OPcache\\CacheStrategy::Immediate") @@ -120,6 +130,7 @@ ZEND_FUNCTION(OPcache_volatile_fetch); ZEND_FUNCTION(OPcache_volatile_fetch_array); ZEND_FUNCTION(OPcache_volatile_exists); ZEND_FUNCTION(OPcache_volatile_lock); +ZEND_FUNCTION(OPcache_volatile_unlock); ZEND_FUNCTION(OPcache_volatile_delete); ZEND_FUNCTION(OPcache_volatile_delete_array); ZEND_FUNCTION(OPcache_volatile_clear); @@ -130,12 +141,14 @@ ZEND_FUNCTION(OPcache_persistent_fetch); ZEND_FUNCTION(OPcache_persistent_fetch_array); ZEND_FUNCTION(OPcache_persistent_exists); ZEND_FUNCTION(OPcache_persistent_lock); +ZEND_FUNCTION(OPcache_persistent_unlock); ZEND_FUNCTION(OPcache_persistent_delete); ZEND_FUNCTION(OPcache_persistent_delete_array); ZEND_FUNCTION(OPcache_persistent_clear); ZEND_FUNCTION(OPcache_persistent_atomic_increment); ZEND_FUNCTION(OPcache_persistent_atomic_decrement); ZEND_FUNCTION(OPcache_persistent_cache_info); +ZEND_METHOD(OPcache_StaticCacheInfo, __construct); ZEND_METHOD(OPcache_VolatileStatic, __construct); static const zend_function_entry ext_functions[] = { @@ -153,6 +166,7 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_fetch_array"), zif_OPcache_volatile_fetch_array, arginfo_OPcache_volatile_fetch_array, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_exists"), zif_OPcache_volatile_exists, arginfo_OPcache_volatile_exists, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_lock"), zif_OPcache_volatile_lock, arginfo_OPcache_volatile_lock, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_unlock"), zif_OPcache_volatile_unlock, arginfo_OPcache_volatile_unlock, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_delete"), zif_OPcache_volatile_delete, arginfo_OPcache_volatile_delete, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_delete_array"), zif_OPcache_volatile_delete_array, arginfo_OPcache_volatile_delete_array, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_clear"), zif_OPcache_volatile_clear, arginfo_OPcache_volatile_clear, 0, NULL, NULL) @@ -163,6 +177,7 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_fetch_array"), zif_OPcache_persistent_fetch_array, arginfo_OPcache_persistent_fetch_array, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_exists"), zif_OPcache_persistent_exists, arginfo_OPcache_persistent_exists, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_lock"), zif_OPcache_persistent_lock, arginfo_OPcache_persistent_lock, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_unlock"), zif_OPcache_persistent_unlock, arginfo_OPcache_persistent_unlock, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_delete"), zif_OPcache_persistent_delete, arginfo_OPcache_persistent_delete, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_delete_array"), zif_OPcache_persistent_delete_array, arginfo_OPcache_persistent_delete_array, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_clear"), zif_OPcache_persistent_clear, arginfo_OPcache_persistent_clear, 0, NULL, NULL) @@ -172,6 +187,11 @@ static const zend_function_entry ext_functions[] = { ZEND_FE_END }; +static const zend_function_entry class_OPcache_StaticCacheInfo_methods[] = { + ZEND_ME(OPcache_StaticCacheInfo, __construct, arginfo_class_OPcache_StaticCacheInfo___construct, ZEND_ACC_PRIVATE) + ZEND_FE_END +}; + static const zend_function_entry class_OPcache_VolatileStatic_methods[] = { ZEND_ME(OPcache_VolatileStatic, __construct, arginfo_class_OPcache_VolatileStatic___construct, ZEND_ACC_PUBLIC) ZEND_FE_END @@ -187,6 +207,76 @@ static zend_class_entry *register_class_OPcache_StaticCacheException(zend_class_ return class_entry; } +static zend_class_entry *register_class_OPcache_StaticCacheInfo(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "OPcache", "StaticCacheInfo", class_OPcache_StaticCacheInfo_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_READONLY_CLASS); + + zval property_enabled_default_value; + ZVAL_UNDEF(&property_enabled_default_value); + zend_string *property_enabled_name = zend_string_init("enabled", sizeof("enabled") - 1, true); + zend_declare_typed_property(class_entry, property_enabled_name, &property_enabled_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_string_release_ex(property_enabled_name, true); + + zval property_available_default_value; + ZVAL_UNDEF(&property_available_default_value); + zend_string *property_available_name = zend_string_init("available", sizeof("available") - 1, true); + zend_declare_typed_property(class_entry, property_available_name, &property_available_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_string_release_ex(property_available_name, true); + + zval property_startup_failed_default_value; + ZVAL_UNDEF(&property_startup_failed_default_value); + zend_string *property_startup_failed_name = zend_string_init("startup_failed", sizeof("startup_failed") - 1, true); + zend_declare_typed_property(class_entry, property_startup_failed_name, &property_startup_failed_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_string_release_ex(property_startup_failed_name, true); + + zval property_backend_initialized_default_value; + ZVAL_UNDEF(&property_backend_initialized_default_value); + zend_string *property_backend_initialized_name = zend_string_init("backend_initialized", sizeof("backend_initialized") - 1, true); + zend_declare_typed_property(class_entry, property_backend_initialized_name, &property_backend_initialized_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_string_release_ex(property_backend_initialized_name, true); + + zval property_configured_memory_default_value; + ZVAL_UNDEF(&property_configured_memory_default_value); + zend_string *property_configured_memory_name = zend_string_init("configured_memory", sizeof("configured_memory") - 1, true); + zend_declare_typed_property(class_entry, property_configured_memory_name, &property_configured_memory_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(property_configured_memory_name, true); + + zval property_shared_memory_default_value; + ZVAL_UNDEF(&property_shared_memory_default_value); + zend_string *property_shared_memory_name = zend_string_init("shared_memory", sizeof("shared_memory") - 1, true); + zend_declare_typed_property(class_entry, property_shared_memory_name, &property_shared_memory_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(property_shared_memory_name, true); + + zval property_entry_count_default_value; + ZVAL_UNDEF(&property_entry_count_default_value); + zend_string *property_entry_count_name = zend_string_init("entry_count", sizeof("entry_count") - 1, true); + zend_declare_typed_property(class_entry, property_entry_count_name, &property_entry_count_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(property_entry_count_name, true); + + zval property_segment_count_default_value; + ZVAL_UNDEF(&property_segment_count_default_value); + zend_string *property_segment_count_name = zend_string_init("segment_count", sizeof("segment_count") - 1, true); + zend_declare_typed_property(class_entry, property_segment_count_name, &property_segment_count_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(property_segment_count_name, true); + + zval property_shared_model_default_value; + ZVAL_UNDEF(&property_shared_model_default_value); + zend_string *property_shared_model_name = zend_string_init("shared_model", sizeof("shared_model") - 1, true); + zend_declare_typed_property(class_entry, property_shared_model_name, &property_shared_model_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release_ex(property_shared_model_name, true); + + zval property_failure_reason_default_value; + ZVAL_UNDEF(&property_failure_reason_default_value); + zend_string *property_failure_reason_name = zend_string_init("failure_reason", sizeof("failure_reason") - 1, true); + zend_declare_typed_property(class_entry, property_failure_reason_name, &property_failure_reason_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + zend_string_release_ex(property_failure_reason_name, true); + + return class_entry; +} + static zend_class_entry *register_class_OPcache_PersistentStatic(void) { zend_class_entry ce, *class_entry; diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt index c8de80d88a46..73e0041b742a 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt @@ -70,7 +70,7 @@ if ($request === 1) { } if ($request === 2) { - echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()['entry_count'], ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; + echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; $last = 0; for ($i = 0; $i < 4; $i++) { @@ -83,7 +83,7 @@ if ($request === 2) { return; } -echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()['entry_count'], ',', count(file($logFile, FILE_IGNORE_NEW_LINES)); +echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)); PHP; $tester = new FPM\Tester($cfg, $code); diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt index 563e39b2bbc5..54e83edb9acc 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt @@ -42,7 +42,7 @@ if ($request === 1) { return; } -echo CombinedBlobState::$propertyCount, ',', CombinedBlobState::$propertyBag['numbers'][0], ',', CombinedBlobState::nextMethod(), ',', OPcache\persistent_cache_info()['entry_count'], "\n"; +echo CombinedBlobState::$propertyCount, ',', CombinedBlobState::$propertyBag['numbers'][0], ',', CombinedBlobState::nextMethod(), ',', OPcache\persistent_cache_info()->entry_count, "\n"; CombinedBlobState::$propertyCount++; CombinedBlobState::$propertyBag['numbers'][] = 11; diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt index d7dda3838147..6013b5fe42c7 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt @@ -28,15 +28,15 @@ class NestedObjectPropertyState NestedObjectPropertyState::$propertyState ??= (object) ['count' => 1]; echo NestedObjectPropertyState::$propertyState->count, "\n"; -echo OPcache\persistent_cache_info()['entry_count'], "\n"; +echo OPcache\persistent_cache_info()->entry_count, "\n"; NestedObjectPropertyState::$propertyState->count++; echo NestedObjectPropertyState::$propertyState->count, "\n"; -echo OPcache\persistent_cache_info()['entry_count'], "\n"; +echo OPcache\persistent_cache_info()->entry_count, "\n"; NestedObjectPropertyState::$propertyState->count = 10; echo NestedObjectPropertyState::$propertyState->count, "\n"; -echo OPcache\persistent_cache_info()['entry_count']; +echo OPcache\persistent_cache_info()->entry_count; PHP; $tester = new FPM\Tester($cfg, $code); diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt index 804dc30b2d28..a810694022a1 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt @@ -28,11 +28,11 @@ class NestedArrayPropertyState NestedArrayPropertyState::$propertyState = ['numbers' => [1]]; echo json_encode(NestedArrayPropertyState::$propertyState['numbers']), "\n"; -echo OPcache\persistent_cache_info()['entry_count'], "\n"; +echo OPcache\persistent_cache_info()->entry_count, "\n"; NestedArrayPropertyState::$propertyState['numbers'][] = 2; echo json_encode(NestedArrayPropertyState::$propertyState['numbers']), "\n"; -echo OPcache\persistent_cache_info()['entry_count']; +echo OPcache\persistent_cache_info()->entry_count; PHP; $tester = new FPM\Tester($cfg, $code); diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_internal_object_method_mutation_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_internal_object_method_mutation_001.phpt index a3016f8a826c..44663f836774 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_internal_object_method_mutation_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_static_internal_object_method_mutation_001.phpt @@ -14,7 +14,7 @@ $cfg = <<count() > 1 ? $value->offsetGet(1) : 'none'; echo "fixed=", $value->count(), ",", $value->offsetGet(0), ",", $second, "\n"; PHP; -$tester = new FPM\Tester($cfg, $code); +$filePrefix = sys_get_temp_dir() . '/opcache_static_cache_internal_object_fpm_' . getmypid() . '.'; +$tester = new FPM\Tester($cfg, $code, fileName: $filePrefix); $tester->start(iniEntries: [ 'opcache.enable' => '1', 'opcache.static_cache.volatile_size_mb' => '32', 'opcache.file_update_protection' => '0', 'opcache.jit' => '0', ]); +register_shutdown_function(static function () use ($filePrefix): void { + foreach (glob($filePrefix . '*') ?: [] as $path) { + @unlink($path); + } +}); $tester->expectLogStartNotices(); function static_cache_internal_object_fpm_request(FPM\Tester $tester, string $query): void { - echo $tester->request($query)->getBody(), "\n"; + echo $tester->request($query, address: '{{ADDR:UDS}}')->getBody(), "\n"; } foreach (['date', 'array', 'fixed'] as $state) { diff --git a/ext/opcache/tests/static_cache_attribute_class_delete_001.phpt b/ext/opcache/tests/static_cache_attribute_class_delete_001.phpt new file mode 100644 index 000000000000..093eb21ad24c --- /dev/null +++ b/ext/opcache/tests/static_cache_attribute_class_delete_001.phpt @@ -0,0 +1,367 @@ +--TEST-- +OPcache static attributes can be deleted by their class names +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + OPcache\persistent_cache_info(), + 'volatile' => OPcache\volatile_cache_info(), + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_store(string $backend, string $key, mixed $value): void +{ + match ($backend) { + 'persistent' => OPcache\persistent_store($key, $value), + 'volatile' => OPcache\volatile_store($key, $value), + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_fetch(string $backend, string $key, mixed $default): mixed +{ + return match ($backend) { + 'persistent' => OPcache\persistent_fetch($key, $default), + 'volatile' => OPcache\volatile_fetch($key, $default), + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_delete(string $backend, string $key_or_class): void +{ + match ($backend) { + 'persistent' => OPcache\persistent_delete($key_or_class), + 'volatile' => OPcache\volatile_delete($key_or_class), + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_blob_class(string $backend): string +{ + return match ($backend) { + 'persistent' => ClassDeletePersistentBlob::class, + 'volatile' => ClassDeleteVolatileBlob::class, + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_members_class(string $backend): string +{ + return match ($backend) { + 'persistent' => ClassDeletePersistentMembers::class, + 'volatile' => ClassDeleteVolatileMembers::class, + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_plain_class(string $backend): string +{ + return match ($backend) { + 'persistent' => ClassDeletePersistentPlain::class, + 'volatile' => ClassDeleteVolatilePlain::class, + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_blob_state(string $backend): array +{ + return match ($backend) { + 'persistent' => [ + ClassDeletePersistentBlob::$value, + ClassDeletePersistentBlob::method(), + ], + 'volatile' => [ + ClassDeleteVolatileBlob::$value, + ClassDeleteVolatileBlob::method(), + ], + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_members_state(string $backend): array +{ + return match ($backend) { + 'persistent' => [ + ClassDeletePersistentMembers::$property, + ClassDeletePersistentMembers::method(), + ], + 'volatile' => [ + ClassDeleteVolatileMembers::$property, + ClassDeleteVolatileMembers::method(), + ], + default => throw new RuntimeException('unknown backend'), + }; +} + +function class_delete_seed_blob(string $backend): void +{ + switch ($backend) { + case 'persistent': + ClassDeletePersistentBlob::$value = 1; + ClassDeletePersistentBlob::method(1); + break; + + case 'volatile': + ClassDeleteVolatileBlob::$value = 1; + ClassDeleteVolatileBlob::method(1); + break; + + default: + throw new RuntimeException('unknown backend'); + } +} + +function class_delete_seed_members(string $backend): void +{ + switch ($backend) { + case 'persistent': + ClassDeletePersistentMembers::$property = 1; + ClassDeletePersistentMembers::method(1); + break; + + case 'volatile': + ClassDeleteVolatileMembers::$property = 1; + ClassDeleteVolatileMembers::method(1); + break; + + default: + throw new RuntimeException('unknown backend'); + } +} + +function class_delete_dump_blob(string $label, string $backend): void +{ + echo $label, '-', $backend, '=', + implode(',', class_delete_blob_state($backend)), + ',count=', class_delete_cache_info($backend)->entry_count, + "\n"; +} + +function class_delete_dump_members(string $label, string $backend): void +{ + echo $label, '-', $backend, '=', + implode(',', class_delete_members_state($backend)), + ',count=', class_delete_cache_info($backend)->entry_count, + "\n"; +} + +$action = $_GET['action'] ?? 'read_blob'; +$backend = $_GET['backend'] ?? 'persistent'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + OPcache\persistent_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed_blob') { + class_delete_seed_blob($backend); + class_delete_dump_blob('seed-blob', $backend); + return; +} + +if ($action === 'read_blob') { + class_delete_dump_blob('read-blob', $backend); + return; +} + +if ($action === 'delete_blob') { + $key_or_class = class_delete_blob_class($backend); + if ($backend === 'volatile') { + $key_or_class = '\\' . $key_or_class; + } + class_delete_delete($backend, $key_or_class); + echo 'delete-blob-', $backend, '=count=', class_delete_cache_info($backend)->entry_count, "\n"; + return; +} + +if ($action === 'seed_members') { + class_delete_seed_members($backend); + class_delete_dump_members('seed-members', $backend); + return; +} + +if ($action === 'read_members') { + class_delete_dump_members('read-members', $backend); + return; +} + +if ($action === 'delete_members') { + class_delete_delete($backend, class_delete_members_class($backend)); + echo 'delete-members-', $backend, '=count=', class_delete_cache_info($backend)->entry_count, "\n"; + return; +} + +if ($action === 'delete_plain') { + class_delete_delete($backend, class_delete_plain_class($backend)); + echo 'delete-plain-', $backend, '=count=', class_delete_cache_info($backend)->entry_count, "\n"; + return; +} + +if ($action === 'noautoload') { + $key = 'ClassDelete\\MissingAutoload'; + class_delete_store($backend, $key, 'autoload'); + spl_autoload_register(static function (string $class): void { + echo 'autoload-', $class, "\n"; + }); + class_delete_delete($backend, $key); + echo 'noautoload-', $backend, '=', + class_delete_fetch($backend, $key, 'missing'), + ',count=', class_delete_cache_info($backend)->entry_count, + "\n"; + return; +} + +throw new RuntimeException('unknown action'); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/static_cache_attribute_class_delete_001.php'; +foreach (['persistent', 'volatile'] as $backend) { + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=seed_blob&backend=' . $backend); + echo file_get_contents($base . '?action=read_blob&backend=' . $backend); + echo file_get_contents($base . '?action=delete_blob&backend=' . $backend); + echo file_get_contents($base . '?action=read_blob&backend=' . $backend); + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=seed_members&backend=' . $backend); + echo file_get_contents($base . '?action=read_members&backend=' . $backend); + echo file_get_contents($base . '?action=delete_members&backend=' . $backend); + echo file_get_contents($base . '?action=read_members&backend=' . $backend); + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=delete_plain&backend=' . $backend); + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=noautoload&backend=' . $backend); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +seed-blob-persistent=1,1,count=1 +read-blob-persistent=1,1,count=1 +delete-blob-persistent=count=0 +read-blob-persistent=0,0,count=0 +reset +seed-members-persistent=1,1,count=2 +read-members-persistent=1,1,count=2 +delete-members-persistent=count=0 +read-members-persistent=0,0,count=0 +reset +delete-plain-persistent=count=0 +reset +noautoload-persistent=missing,count=0 +reset +seed-blob-volatile=1,1,count=1 +read-blob-volatile=1,1,count=1 +delete-blob-volatile=count=0 +read-blob-volatile=0,0,count=0 +reset +seed-members-volatile=1,1,count=2 +read-members-volatile=1,1,count=2 +delete-members-volatile=count=0 +read-members-volatile=0,0,count=0 +reset +delete-plain-volatile=count=0 +reset +noautoload-volatile=missing,count=0 diff --git a/ext/opcache/tests/static_cache_attribute_exact_key_delete_001.phpt b/ext/opcache/tests/static_cache_attribute_exact_key_delete_001.phpt new file mode 100644 index 000000000000..b65403b0c5d5 --- /dev/null +++ b/ext/opcache/tests/static_cache_attribute_exact_key_delete_001.phpt @@ -0,0 +1,227 @@ +--TEST-- +OPcache static property and method attributes can be deleted by documented exact cache keys +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + OPcache\persistent_cache_info(), + 'volatile' => OPcache\volatile_cache_info(), + default => throw new RuntimeException('unknown backend'), + }; +} + +function exact_key_state(string $backend): array +{ + return match ($backend) { + 'persistent' => [ + ExactKeyPersistentClassState::$value, + ExactKeyPersistentClassState::method(), + ExactKeyPersistentPropertyState::$value, + ExactKeyPersistentMethodState::method(), + ], + 'volatile' => [ + ExactKeyVolatileClassState::$value, + ExactKeyVolatileClassState::method(), + ExactKeyVolatilePropertyState::$value, + ExactKeyVolatileMethodState::method(), + ], + default => throw new RuntimeException('unknown backend'), + }; +} + +function exact_key_seed(string $backend): void +{ + switch ($backend) { + case 'persistent': + ExactKeyPersistentClassState::$value = 1; + ExactKeyPersistentClassState::method(1); + ExactKeyPersistentPropertyState::$value = 1; + ExactKeyPersistentMethodState::method(1); + return; + + case 'volatile': + ExactKeyVolatileClassState::$value = 1; + ExactKeyVolatileClassState::method(1); + ExactKeyVolatilePropertyState::$value = 1; + ExactKeyVolatileMethodState::method(1); + return; + + default: + throw new RuntimeException('unknown backend'); + } +} + +function exact_key_delete_individual(string $backend): void +{ + switch ($backend) { + case 'persistent': + OPcache\persistent_delete_array([ + 'persistent_static:ExactKeyPersistentPropertyState::$value', + 'persistent_static:ExactKeyPersistentMethodState::method()::$value', + ]); + return; + + case 'volatile': + OPcache\volatile_delete_array([ + 'volatile_static:ExactKeyVolatilePropertyState::$value', + 'volatile_static:ExactKeyVolatileMethodState::method()::$value', + ]); + return; + + default: + throw new RuntimeException('unknown backend'); + } +} + +function exact_key_dump(string $label, string $backend): void +{ + echo $label, '-', $backend, '=', + implode(',', exact_key_state($backend)), + ',count=', exact_key_cache_info($backend)->entry_count, + "\n"; +} + +$action = $_GET['action'] ?? 'read'; +$backend = $_GET['backend'] ?? 'persistent'; + +if ($action === 'reset') { + OPcache\volatile_clear(); + OPcache\persistent_clear(); + opcache_reset(); + echo "reset\n"; + return; +} + +if ($action === 'seed') { + exact_key_seed($backend); + exact_key_dump('seed', $backend); + return; +} + +if ($action === 'delete_individual') { + exact_key_delete_individual($backend); + echo 'delete-individual-', $backend, '=count=', exact_key_cache_info($backend)->entry_count, "\n"; + return; +} + +exact_key_dump('read', $backend); +PHP); + +$php = getenv('TEST_PHP_EXECUTABLE'); +if ($php) { + $php = realpath(__DIR__ . '/../../../' . $php) ?: $php; + putenv('TEST_PHP_EXECUTABLE=' . $php); +} + +include 'php_cli_server.inc'; +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); + +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/static_cache_attribute_exact_key_delete_001.php'; +foreach (['persistent', 'volatile'] as $backend) { + echo file_get_contents($base . '?action=reset'); + echo file_get_contents($base . '?action=seed&backend=' . $backend); + echo file_get_contents($base . '?action=read&backend=' . $backend); + echo file_get_contents($base . '?action=delete_individual&backend=' . $backend); + echo file_get_contents($base . '?action=read&backend=' . $backend); +} + +?> +--CLEAN-- + +--EXPECT-- +reset +seed-persistent=1,1,1,1,count=3 +read-persistent=1,1,1,1,count=3 +delete-individual-persistent=count=1 +read-persistent=1,1,0,0,count=1 +reset +seed-volatile=1,1,1,1,count=3 +read-volatile=1,1,1,1,count=3 +delete-individual-volatile=count=1 +read-volatile=1,1,0,0,count=1 diff --git a/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt index 6f15683579a0..64071e844222 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt @@ -30,51 +30,126 @@ class StringableKey } } +class LoadedClassKey +{ +} + foreach (['volatile', 'persistent'] as $backend) { echo $backend, "\n"; + $reservedClassKey = $backend . '_static_class:LoadedClassKey'; + $loadedClassKey = LoadedClassKey::class; $store = $backend === 'volatile' ? OPcache\volatile_store(...) : OPcache\persistent_store(...); $storeArray = $backend === 'volatile' ? OPcache\volatile_store_array(...) : OPcache\persistent_store_array(...); $fetch = $backend === 'volatile' ? OPcache\volatile_fetch(...) : OPcache\persistent_fetch(...); $fetchArray = $backend === 'volatile' ? OPcache\volatile_fetch_array(...) : OPcache\persistent_fetch_array(...); $exists = $backend === 'volatile' ? OPcache\volatile_exists(...) : OPcache\persistent_exists(...); $lock = $backend === 'volatile' ? OPcache\volatile_lock(...) : OPcache\persistent_lock(...); + $unlock = $backend === 'volatile' ? OPcache\volatile_unlock(...) : OPcache\persistent_unlock(...); $delete = $backend === 'volatile' ? OPcache\volatile_delete(...) : OPcache\persistent_delete(...); $deleteArray = $backend === 'volatile' ? OPcache\volatile_delete_array(...) : OPcache\persistent_delete_array(...); dump_error('store-empty', static fn () => $store('', 'value')); dump_error('store-array-empty', static fn () => $storeArray(['' => 'value'])); + dump_error('store-reserved-class', static fn () => $store($reservedClassKey, 'value')); + dump_error('store-array-reserved-class', static fn () => $storeArray([$reservedClassKey => 'value'])); + dump_error('store-loaded-class', static fn () => $store($loadedClassKey, 'value')); + dump_error('store-array-loaded-class', static fn () => $storeArray([$loadedClassKey => 'value'])); dump_error('fetch-empty', static fn () => $fetch('')); dump_error('fetch-array-empty', static fn () => $fetchArray([''])); dump_error('fetch-array-object', static fn () => $fetchArray([new StringableKey()])); + dump_error('fetch-reserved-class', static fn () => $fetch($reservedClassKey)); + dump_error('fetch-array-reserved-class', static fn () => $fetchArray([$reservedClassKey])); + dump_error('fetch-loaded-class', static fn () => $fetch($loadedClassKey)); + dump_error('fetch-array-loaded-class', static fn () => $fetchArray([$loadedClassKey])); dump_error('exists-empty', static fn () => $exists('')); + dump_error('exists-reserved-class', static fn () => $exists($reservedClassKey)); + dump_error('exists-loaded-class', static fn () => $exists($loadedClassKey)); dump_error('lock-empty', static fn () => $lock('')); + dump_error('lock-negative-lease', static fn () => $lock('key', -1)); + dump_error('lock-reserved-class', static fn () => $lock($reservedClassKey)); + dump_error('lock-loaded-class', static fn () => $lock($loadedClassKey)); + dump_error('unlock-empty', static fn () => $unlock('')); + dump_error('unlock-reserved-class', static fn () => $unlock($reservedClassKey)); + dump_error('unlock-loaded-class', static fn () => $unlock($loadedClassKey)); dump_error('delete-empty', static fn () => $delete('')); + dump_error('delete-reserved-class', static fn () => $delete($reservedClassKey)); dump_error('delete-array-empty', static fn () => $deleteArray([''])); dump_error('delete-array-object', static fn () => $deleteArray([new StringableKey()])); + dump_error('delete-array-reserved-class', static fn () => $deleteArray([$reservedClassKey])); + dump_error('delete-array-loaded-class', static fn () => $deleteArray([$loadedClassKey])); + + if ($backend === 'persistent') { + dump_error('atomic-increment-reserved-class', static fn () => OPcache\persistent_atomic_increment($reservedClassKey)); + dump_error('atomic-decrement-reserved-class', static fn () => OPcache\persistent_atomic_decrement($reservedClassKey)); + dump_error('atomic-increment-loaded-class', static fn () => OPcache\persistent_atomic_increment($loadedClassKey)); + dump_error('atomic-decrement-loaded-class', static fn () => OPcache\persistent_atomic_decrement($loadedClassKey)); + } } ?> --EXPECTF-- volatile store-empty: ValueError: OPcache\volatile_store(): Argument #1 ($key) must be a non-empty string -store-array-empty: ValueError: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys +store-array-empty: ValueError: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names +store-reserved-class: ValueError: OPcache\volatile_store(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +store-array-reserved-class: ValueError: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names +store-loaded-class: ValueError: OPcache\volatile_store(): Argument #1 ($key) must not be a loaded class name +store-array-loaded-class: ValueError: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names fetch-empty: ValueError: OPcache\volatile_fetch(): Argument #1 ($key) must be a non-empty string -fetch-array-empty: ValueError: OPcache\volatile_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys -fetch-array-object: ValueError: OPcache\volatile_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +fetch-array-empty: ValueError: OPcache\volatile_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-array-object: ValueError: OPcache\volatile_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-reserved-class: ValueError: OPcache\volatile_fetch(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +fetch-array-reserved-class: ValueError: OPcache\volatile_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-loaded-class: ValueError: OPcache\volatile_fetch(): Argument #1 ($key) must not be a loaded class name +fetch-array-loaded-class: ValueError: OPcache\volatile_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names exists-empty: ValueError: OPcache\volatile_exists(): Argument #1 ($key) must be a non-empty string +exists-reserved-class: ValueError: OPcache\volatile_exists(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +exists-loaded-class: ValueError: OPcache\volatile_exists(): Argument #1 ($key) must not be a loaded class name lock-empty: ValueError: OPcache\volatile_lock(): Argument #1 ($key) must be a non-empty string -delete-empty: ValueError: OPcache\volatile_delete(): Argument #1 ($key) must be a non-empty string -delete-array-empty: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys -delete-array-object: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +lock-negative-lease: ValueError: OPcache\volatile_lock(): Argument #2 ($lease) must be greater than or equal to 0 +lock-reserved-class: ValueError: OPcache\volatile_lock(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +lock-loaded-class: ValueError: OPcache\volatile_lock(): Argument #1 ($key) must not be a loaded class name +unlock-empty: ValueError: OPcache\volatile_unlock(): Argument #1 ($key) must be a non-empty string +unlock-reserved-class: ValueError: OPcache\volatile_unlock(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +unlock-loaded-class: ValueError: OPcache\volatile_unlock(): Argument #1 ($key) must not be a loaded class name +delete-empty: ValueError: OPcache\volatile_delete(): Argument #1 ($key_or_class) must be a non-empty string +delete-reserved-class: ValueError: OPcache\volatile_delete(): Argument #1 ($key_or_class) must not start with a reserved static-cache class key prefix +delete-array-empty: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-object: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-reserved-class: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-loaded-class: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names persistent store-empty: ValueError: OPcache\persistent_store(): Argument #1 ($key) must be a non-empty string -store-array-empty: ValueError: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys +store-array-empty: ValueError: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names +store-reserved-class: ValueError: OPcache\persistent_store(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +store-array-reserved-class: ValueError: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names +store-loaded-class: ValueError: OPcache\persistent_store(): Argument #1 ($key) must not be a loaded class name +store-array-loaded-class: ValueError: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names fetch-empty: ValueError: OPcache\persistent_fetch(): Argument #1 ($key) must be a non-empty string -fetch-array-empty: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys -fetch-array-object: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +fetch-array-empty: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-array-object: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-reserved-class: ValueError: OPcache\persistent_fetch(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +fetch-array-reserved-class: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-loaded-class: ValueError: OPcache\persistent_fetch(): Argument #1 ($key) must not be a loaded class name +fetch-array-loaded-class: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names exists-empty: ValueError: OPcache\persistent_exists(): Argument #1 ($key) must be a non-empty string +exists-reserved-class: ValueError: OPcache\persistent_exists(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +exists-loaded-class: ValueError: OPcache\persistent_exists(): Argument #1 ($key) must not be a loaded class name lock-empty: ValueError: OPcache\persistent_lock(): Argument #1 ($key) must be a non-empty string -delete-empty: ValueError: OPcache\persistent_delete(): Argument #1 ($key) must be a non-empty string -delete-array-empty: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys -delete-array-object: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys +lock-negative-lease: ValueError: OPcache\persistent_lock(): Argument #2 ($lease) must be greater than or equal to 0 +lock-reserved-class: ValueError: OPcache\persistent_lock(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +lock-loaded-class: ValueError: OPcache\persistent_lock(): Argument #1 ($key) must not be a loaded class name +unlock-empty: ValueError: OPcache\persistent_unlock(): Argument #1 ($key) must be a non-empty string +unlock-reserved-class: ValueError: OPcache\persistent_unlock(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +unlock-loaded-class: ValueError: OPcache\persistent_unlock(): Argument #1 ($key) must not be a loaded class name +delete-empty: ValueError: OPcache\persistent_delete(): Argument #1 ($key_or_class) must be a non-empty string +delete-reserved-class: ValueError: OPcache\persistent_delete(): Argument #1 ($key_or_class) must not start with a reserved static-cache class key prefix +delete-array-empty: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-object: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-reserved-class: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-loaded-class: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +atomic-increment-reserved-class: ValueError: OPcache\persistent_atomic_increment(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +atomic-decrement-reserved-class: ValueError: OPcache\persistent_atomic_decrement(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +atomic-increment-loaded-class: ValueError: OPcache\persistent_atomic_increment(): Argument #1 ($key) must not be a loaded class name +atomic-decrement-loaded-class: ValueError: OPcache\persistent_atomic_decrement(): Argument #1 ($key) must not be a loaded class name diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_lease_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_lease_001.phpt new file mode 100644 index 000000000000..ee034ca08111 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_lease_001.phpt @@ -0,0 +1,89 @@ +--TEST-- +OPcache explicit cache lock leases survive request shutdown until expiry +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + +--EXPECT-- +volatile +child locked: true +bool(false) +bool(true) +bool(true) +persistent +child locked: true +bool(false) +bool(true) +bool(true) +--CLEAN-- + diff --git a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt index da6e31c55e0d..204ec2269d83 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache explicit cache signatures expose storable values and array fallbacks +OPcache explicit cache signatures expose public API types --EXTENSIONS-- opcache --FILE-- @@ -39,9 +39,17 @@ foreach ([ 'OPcache\\volatile_store' => ['value'], 'OPcache\\volatile_fetch' => ['default'], 'OPcache\\volatile_fetch_array' => ['default'], + 'OPcache\\volatile_lock' => ['lease'], + 'OPcache\\volatile_unlock' => [], + 'OPcache\\volatile_cache_info' => [], 'OPcache\\persistent_store' => ['value'], 'OPcache\\persistent_fetch' => ['default'], 'OPcache\\persistent_fetch_array' => ['default'], + 'OPcache\\persistent_lock' => ['lease'], + 'OPcache\\persistent_unlock' => [], + 'OPcache\\persistent_atomic_increment' => ['step'], + 'OPcache\\persistent_atomic_decrement' => ['step'], + 'OPcache\\persistent_cache_info' => [], ] as $function => $parameters) { $reflection = new ReflectionFunction($function); $parts = [$function]; @@ -63,6 +71,14 @@ foreach ([ OPcache\volatile_store $value=null|bool|int|float|string|array|object params=2/3 return=bool OPcache\volatile_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object OPcache\volatile_fetch_array $default=?array params=1/2 return=?array +OPcache\volatile_lock $lease=int params=1/2 return=bool +OPcache\volatile_unlock params=1/1 return=bool +OPcache\volatile_cache_info params=0/0 return=OPcache\StaticCacheInfo OPcache\persistent_store $value=null|bool|int|float|string|array|object params=2/2 return=void OPcache\persistent_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object OPcache\persistent_fetch_array $default=?array params=1/2 return=?array +OPcache\persistent_lock $lease=int params=1/2 return=bool +OPcache\persistent_unlock params=1/1 return=bool +OPcache\persistent_atomic_increment $step=int params=1/2 return=int +OPcache\persistent_atomic_decrement $step=int params=1/2 return=int +OPcache\persistent_cache_info params=0/0 return=OPcache\StaticCacheInfo diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt index 7e34a185aa43..95444eba2554 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt @@ -33,7 +33,7 @@ foreach ([ ?> --EXPECT-- -volatile: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys +volatile: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names string(7) "missing" -persistent: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys +persistent: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names string(7) "missing" diff --git a/ext/opcache/tests/static_cache_explicit_cache_unlock_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_unlock_001.phpt new file mode 100644 index 000000000000..a7ba2b6e36f3 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_unlock_001.phpt @@ -0,0 +1,88 @@ +--TEST-- +OPcache explicit cache locks can be manually unlocked +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.static_cache.persistent_size_mb=32 +--FILE-- + +--EXPECT-- +volatile +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) +bool(true) +bool(false) +string(9) "published" +persistent +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) +bool(true) +bool(false) +string(9) "published" diff --git a/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt index d2d085885e14..1132cfb1b8ee 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache persistent atomic increment creates missing keys without TTL +OPcache persistent atomic increment and decrement create missing keys --EXTENSIONS-- opcache --INI-- @@ -9,8 +9,6 @@ opcache.static_cache.persistent_size_mb=32 --FILE-- getMessage(), "\n"; -} +var_dump(OPcache\persistent_atomic_decrement('missing_down')); +var_dump(OPcache\persistent_fetch('missing_down')); +var_dump(OPcache\persistent_atomic_decrement('missing_down', 4)); +var_dump(OPcache\persistent_fetch('missing_down')); try { OPcache\persistent_atomic_increment('extra', 1, 1); @@ -45,5 +41,8 @@ bool(true) int(11) int(11) bool(true) -Cache key "missing_down" was not found +int(-1) +int(-1) +int(-5) +int(-5) too-many-args diff --git a/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt index 047fc02558c7..f63ab81d7897 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt @@ -27,7 +27,7 @@ $persistentInfo = OPcache\persistent_cache_info(); $status = opcache_get_status(); $config = opcache_get_configuration(); -echo $volatileInfo['entry_count'], ',', $persistentInfo['entry_count'], ',', OPcache\volatile_fetch('explicit-volatile-key'), ',', ++PersistentInfoCounter::$value, "\n"; +echo $volatileInfo->entry_count, ',', $persistentInfo->entry_count, ',', OPcache\volatile_fetch('explicit-volatile-key'), ',', ++PersistentInfoCounter::$value, "\n"; var_dump($status['volatile_cache'] == $volatileInfo); var_dump($status['persistent_cache'] == $persistentInfo); var_dump($config['directives']['opcache.static_cache.volatile_size_mb']); diff --git a/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt b/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt index faf2af133f21..188a0ae14388 100644 --- a/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt @@ -66,7 +66,7 @@ if ($action === 'inspect') { PropertyScopeState::$normalValue, ',', MethodScopeState::$normalValue, ',', MethodScopeState::normalMethodValue(), ',', - OPcache\persistent_cache_info()['entry_count'], "\n"; + OPcache\persistent_cache_info()->entry_count, "\n"; return; } diff --git a/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt b/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt index d7828b8dbfdf..32499e4a0755 100644 --- a/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt @@ -47,7 +47,7 @@ if ($action === 'seed') { $probe = new CaptureMissProbe(); $probe->bag[] = 'seed'; var_dump(OPcache\volatile_store('capture_miss_explicit', $probe)); - echo 'volatile_entries=', OPcache\volatile_cache_info()['entry_count'], "\n"; + echo 'volatile_entries=', OPcache\volatile_cache_info()->entry_count, "\n"; return; } @@ -62,8 +62,8 @@ if ($action === 'miss_then_explicit_mutate') { $explicit->bag[] = 'mutated outside static attribute'; echo 'explicit_bag=', count($explicit->bag), "\n"; - echo 'volatile_entries=', OPcache\volatile_cache_info()['entry_count'], "\n"; - echo 'persistent_entries=', OPcache\persistent_cache_info()['entry_count'], "\n"; + echo 'volatile_entries=', OPcache\volatile_cache_info()->entry_count, "\n"; + echo 'persistent_entries=', OPcache\persistent_cache_info()->entry_count, "\n"; return; } @@ -73,8 +73,8 @@ if ($action === 'read_static') { 'persistent' => CaptureMissPersistentMethod::touch(), default => throw new RuntimeException('unknown backend'), }; - echo 'volatile_entries=', OPcache\volatile_cache_info()['entry_count'], "\n"; - echo 'persistent_entries=', OPcache\persistent_cache_info()['entry_count'], "\n"; + echo 'volatile_entries=', OPcache\volatile_cache_info()->entry_count, "\n"; + echo 'persistent_entries=', OPcache\persistent_cache_info()->entry_count, "\n"; return; } diff --git a/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt index 313d26741e42..9f49f372dbfe 100644 --- a/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt @@ -59,7 +59,7 @@ if ($request === 1) { } if ($request === 2) { - echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()['entry_count'], ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; + echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; $last = 0; for ($i = 0; $i < 4; $i++) { @@ -72,7 +72,7 @@ if ($request === 2) { return; } -echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()['entry_count'], ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; +echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; PHP); $php = getenv('TEST_PHP_EXECUTABLE'); diff --git a/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt index 1c375286a70a..bd7d7c57fad0 100644 --- a/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt @@ -31,7 +31,7 @@ if ($request === 1) { return; } -echo CombinedBlobState::$propertyCount, ',', CombinedBlobState::$propertyBag['numbers'][0], ',', CombinedBlobState::nextMethod(), ',', OPcache\persistent_cache_info()['entry_count'], "\n"; +echo CombinedBlobState::$propertyCount, ',', CombinedBlobState::$propertyBag['numbers'][0], ',', CombinedBlobState::nextMethod(), ',', OPcache\persistent_cache_info()->entry_count, "\n"; CombinedBlobState::$propertyCount++; CombinedBlobState::$propertyBag['numbers'][] = 11; diff --git a/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt index ed2a25b1fbc7..67f797c45a01 100644 --- a/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt @@ -20,15 +20,15 @@ class NestedObjectPropertyState NestedObjectPropertyState::$propertyState ??= (object) ['count' => 1]; var_dump(NestedObjectPropertyState::$propertyState->count); -var_dump(OPcache\persistent_cache_info()['entry_count']); +var_dump(OPcache\persistent_cache_info()->entry_count); NestedObjectPropertyState::$propertyState->count++; var_dump(NestedObjectPropertyState::$propertyState->count); -var_dump(OPcache\persistent_cache_info()['entry_count']); +var_dump(OPcache\persistent_cache_info()->entry_count); NestedObjectPropertyState::$propertyState->count = 10; var_dump(NestedObjectPropertyState::$propertyState->count); -var_dump(OPcache\persistent_cache_info()['entry_count']); +var_dump(OPcache\persistent_cache_info()->entry_count); ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt b/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt index 6ffff4972b54..fd162399a7c3 100644 --- a/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt @@ -28,7 +28,7 @@ if ($action === 'seed') { NestedSnapshotPropertyState::$propertyState = ['numbers' => [1]]; NestedSnapshotPropertyState::$propertyState['numbers'][] = 2; echo 'seed=', implode(',', NestedSnapshotPropertyState::$propertyState['numbers']), "\n"; - echo 'entries=', OPcache\persistent_cache_info()['entry_count'], "\n"; + echo 'entries=', OPcache\persistent_cache_info()->entry_count, "\n"; return; } diff --git a/ext/opcache/tests/static_cache_startup_failure_001.phpt b/ext/opcache/tests/static_cache_startup_failure_001.phpt index fea81428e60c..1e5fe35dd252 100644 --- a/ext/opcache/tests/static_cache_startup_failure_001.phpt +++ b/ext/opcache/tests/static_cache_startup_failure_001.phpt @@ -16,20 +16,20 @@ $status = opcache_get_status(); $volatileInfo = OPcache\volatile_cache_info(); $persistentInfo = OPcache\persistent_cache_info(); -var_dump($status['volatile_cache']['enabled']); -var_dump($status['volatile_cache']['available']); -var_dump($status['volatile_cache']['startup_failed']); -var_dump($status['volatile_cache']['backend_initialized']); -var_dump($status['volatile_cache']['configured_memory']); -var_dump($status['persistent_cache']['enabled']); -var_dump($status['persistent_cache']['available']); -var_dump($status['persistent_cache']['startup_failed']); -var_dump($status['persistent_cache']['backend_initialized']); -var_dump($status['persistent_cache']['configured_memory']); +var_dump($status['volatile_cache']->enabled); +var_dump($status['volatile_cache']->available); +var_dump($status['volatile_cache']->startup_failed); +var_dump($status['volatile_cache']->backend_initialized); +var_dump($status['volatile_cache']->configured_memory); +var_dump($status['persistent_cache']->enabled); +var_dump($status['persistent_cache']->available); +var_dump($status['persistent_cache']->startup_failed); +var_dump($status['persistent_cache']->backend_initialized); +var_dump($status['persistent_cache']->configured_memory); var_dump($volatileInfo == $status['volatile_cache']); var_dump($persistentInfo == $status['persistent_cache']); -var_dump($volatileInfo['failure_reason']); -var_dump($persistentInfo['failure_reason']); +var_dump($volatileInfo->failure_reason); +var_dump($persistentInfo->failure_reason); try { OPcache\volatile_store('key', 'value'); diff --git a/ext/opcache/tests/static_cache_volatile_cache_info_surface_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_info_surface_001.phpt index 961ea89fc0b4..f3417d3b2456 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_info_surface_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_info_surface_001.phpt @@ -14,16 +14,29 @@ $status = opcache_get_status(); $info = OPcache\volatile_cache_info(); var_dump($config['directives']['opcache.static_cache.volatile_size_mb']); -var_dump($status['volatile_cache']['enabled']); -var_dump($status['volatile_cache']['available']); -var_dump($status['volatile_cache']['startup_failed']); -var_dump($status['volatile_cache']['backend_initialized']); -var_dump($status['volatile_cache']['configured_memory']); -var_dump($status['volatile_cache']['shared_memory']); -var_dump($status['volatile_cache']['segment_count'] > 0); -var_dump(is_string($status['volatile_cache']['shared_model'])); +var_dump($status['volatile_cache']->enabled); +var_dump($status['volatile_cache']->available); +var_dump($status['volatile_cache']->startup_failed); +var_dump($status['volatile_cache']->backend_initialized); +var_dump($status['volatile_cache']->configured_memory); +var_dump($status['volatile_cache']->shared_memory); +var_dump($status['volatile_cache']->segment_count > 0); +var_dump(is_string($status['volatile_cache']->shared_model)); var_dump($info == $status['volatile_cache']); -var_dump(array_key_exists('failure_reason', $info)); +var_dump($info instanceof OPcache\StaticCacheInfo); +var_dump((new ReflectionClass($info))->isReadOnly()); +var_dump(property_exists($info, 'failure_reason')); +var_dump($info->failure_reason); +try { + $info->entry_count = 0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +try { + new OPcache\StaticCacheInfo(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} ?> --EXPECTF-- @@ -37,4 +50,9 @@ int(33554432) bool(true) bool(true) bool(true) -bool(false) +bool(true) +bool(true) +bool(true) +NULL +Cannot modify readonly property OPcache\StaticCacheInfo::$entry_count +Call to private OPcache\StaticCacheInfo::__construct() from global scope diff --git a/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt index 171873cfd6f8..1dae05460fac 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt @@ -63,8 +63,8 @@ function dump_static_cache_exception(Closure $callback): void dump_type_error(static fn () => OPcache\volatile_store('resource', $resource)); var_dump(OPcache\volatile_fetch('resource', 'missing')); -dump_type_error(static fn () => OPcache\volatile_store('closure', $closure)); -var_dump(OPcache\volatile_fetch('closure', 'missing')); +dump_type_error(static fn () => OPcache\volatile_store('closure-value', $closure)); +var_dump(OPcache\volatile_fetch('closure-value', 'missing')); var_dump(OPcache\volatile_store_array(['nested-resource' => ['value' => $resource]])); var_dump(OPcache\volatile_fetch('nested-resource', 'missing')); @@ -93,7 +93,7 @@ dump_type_error(static fn () => OPcache\volatile_fetch('missing', $resource)); dump_type_error(static fn () => OPcache\volatile_fetch('missing', $closure)); dump_type_error(static fn () => OPcache\persistent_store('resource', $resource)); -dump_type_error(static fn () => OPcache\persistent_store('closure', $closure)); +dump_type_error(static fn () => OPcache\persistent_store('closure-value', $closure)); dump_static_cache_exception(static fn () => OPcache\persistent_store_array(['nested-resource' => ['value' => $resource]])); dump_static_cache_exception(static fn () => OPcache\persistent_store_array(['nested-closure' => ['value' => $closure]])); dump_static_cache_exception(static fn () => OPcache\persistent_store('object-resource', $resource_object)); diff --git a/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_fork_001.phpt b/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_fork_001.phpt new file mode 100644 index 000000000000..e8498a3c5c54 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_fork_001.phpt @@ -0,0 +1,159 @@ +--TEST-- +OPcache VolatileStatic tracks internal object method mutations across fork +--EXTENSIONS-- +opcache +pcntl +spl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +opcache.file_update_protection=0 +opcache.jit=0 +--FILE-- + 0]); + return $value; + } +} + +class VolatileStaticForkFixedArrayMethodState +{ + #[OPcache\VolatileStatic(strategy: OPcache\CacheStrategy::Tracking)] + public static function value(): SplFixedArray + { + static $value = null; + + $value ??= SplFixedArray::fromArray(['seed']); + return $value; + } +} + +function static_cache_internal_object_wait_for_file(string $path): void +{ + $deadline = microtime(true) + 5.0; + + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + + usleep(1000); + } +} + +function static_cache_internal_object_fork_case(string $state): void +{ + $prefix = sys_get_temp_dir() . '/opcache_volatile_static_internal_object_method_mutation_fork_' . getmypid() . '_' . $state; + $readyFile = $prefix . '.ready'; + $resultFile = $prefix . '.result'; + @unlink($readyFile); + @unlink($resultFile); + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + if ($state === 'date') { + $value = VolatileStaticForkDateMethodState::value(); + $value->modify('+1 day'); + } elseif ($state === 'array') { + $value = VolatileStaticForkArrayObjectMethodState::value(); + $value->offsetSet('count', $value->offsetGet('count') + 1); + $value->append('tail'); + } else { + $value = VolatileStaticForkFixedArrayMethodState::value(); + $value->offsetSet(0, 'changed'); + $value->setSize(2); + $value->offsetSet(1, 'tail'); + } + + file_put_contents($readyFile, 'ready'); + exit(0); + } + + static_cache_internal_object_wait_for_file($readyFile); + pcntl_waitpid($pid, $status); + @unlink($readyFile); + + if (!pcntl_wifexited($status) || pcntl_wexitstatus($status) !== 0) { + throw new RuntimeException('child process failed'); + } + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + ob_start(); + static_cache_internal_object_dump_case($state); + file_put_contents($resultFile, ob_get_clean()); + exit(0); + } + + pcntl_waitpid($pid, $status); + if (!pcntl_wifexited($status) || pcntl_wexitstatus($status) !== 0) { + throw new RuntimeException('reader process failed'); + } + + echo file_get_contents($resultFile); + @unlink($resultFile); +} + +function static_cache_internal_object_dump_case(string $state): void +{ + if ($state === 'date') { + $value = VolatileStaticForkDateMethodState::value(); + echo "date=", $value->format('Y-m-d'), "\n"; + return; + } + + if ($state === 'array') { + $value = VolatileStaticForkArrayObjectMethodState::value(); + echo "array=", $value->offsetGet('count'), ",", count($value), "\n"; + return; + } + + $value = VolatileStaticForkFixedArrayMethodState::value(); + $second = $value->count() > 1 ? $value->offsetGet(1) : 'none'; + echo "fixed=", $value->count(), ",", $value->offsetGet(0), ",", $second, "\n"; +} + +foreach (['date', 'array', 'fixed'] as $state) { + static_cache_internal_object_fork_case($state); +} + +?> +--EXPECT-- +date=2026-01-02 +array=1,2 +fixed=2,changed,tail diff --git a/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt b/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt index 2c18d31cf0b2..6d88779b3806 100644 --- a/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt @@ -219,7 +219,7 @@ if ($action === 'seed' || $action === 'mutate_after_fetch') { cached_strategy_dump($case, $values); if ($action === 'read') { - echo 'cache=', OPcache\volatile_cache_info()['entry_count'], ',', OPcache\persistent_cache_info()['entry_count'], "\n"; + echo 'cache=', OPcache\volatile_cache_info()->entry_count, ',', OPcache\persistent_cache_info()->entry_count, "\n"; } PHP); diff --git a/ext/opcache/tests/static_cache_windows_backend_001.phpt b/ext/opcache/tests/static_cache_windows_backend_001.phpt index ed1003fd101e..344361530bb8 100644 --- a/ext/opcache/tests/static_cache_windows_backend_001.phpt +++ b/ext/opcache/tests/static_cache_windows_backend_001.phpt @@ -19,12 +19,12 @@ opcache.static_cache.persistent_size_mb=32 $volatileInfo = OPcache\volatile_cache_info(); $persistentInfo = OPcache\persistent_cache_info(); -var_dump($volatileInfo['available']); -var_dump($persistentInfo['available']); -var_dump($volatileInfo['shared_model']); -var_dump($persistentInfo['shared_model']); -var_dump($volatileInfo['shared_memory']); -var_dump($persistentInfo['shared_memory']); +var_dump($volatileInfo->available); +var_dump($persistentInfo->available); +var_dump($volatileInfo->shared_model); +var_dump($persistentInfo->shared_model); +var_dump($volatileInfo->shared_memory); +var_dump($persistentInfo->shared_memory); OPcache\volatile_clear(); OPcache\persistent_clear(); diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index 280ba86d47ae..89d3a532d4e7 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -43,11 +43,16 @@ zend_class_entry *zend_opcache_static_cache_exception_ce; zend_class_entry *zend_opcache_static_cache_strategy_ce; +zend_class_entry *zend_opcache_static_cache_info_ce; static zend_class_entry *zend_opcache_static_cache_persistent_attribute_ce; static zend_class_entry *zend_opcache_static_cache_volatile_static_attribute_ce; static zend_class_entry *zend_opcache_static_cache_safe_direct_attribute_ce; +ZEND_METHOD(OPcache_StaticCacheInfo, __construct) +{ +} + zend_opcache_static_cache_context zend_opcache_static_cache_volatile_context_state = { {0}, {0}, "volatile cache", "opcache_volatile_cache_lock", #ifndef ZEND_WIN32 @@ -121,7 +126,22 @@ ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_entry_locks = NULL; ZEND_EXT_TLS uint32_t zend_opcache_static_cache_volatile_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; ZEND_EXT_TLS uint32_t zend_opcache_static_cache_persistent_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; -static zend_always_inline bool zend_opcache_static_cache_validate_key(zend_string *key, uint32_t arg_num) +static zend_always_inline bool zend_opcache_static_cache_key_has_reserved_class_prefix(zend_string *key) +{ + return zend_string_starts_with_literal(key, "persistent_static_class:") || + zend_string_starts_with_literal(key, "volatile_static_class:") + ; +} + +static zend_always_inline zend_class_entry *zend_opcache_static_cache_lookup_loaded_class_key(zend_string *key) +{ + return zend_lookup_class_ex(key, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); +} + +static zend_always_inline bool zend_opcache_static_cache_validate_key_ex( + zend_string *key, + uint32_t arg_num, + bool allow_class_selector) { if (ZSTR_LEN(key) == 0) { zend_argument_value_error(arg_num, "must be a non-empty string"); @@ -129,16 +149,41 @@ static zend_always_inline bool zend_opcache_static_cache_validate_key(zend_strin return false; } + if (zend_opcache_static_cache_key_has_reserved_class_prefix(key)) { + zend_argument_value_error(arg_num, "must not start with a reserved static-cache class key prefix"); + + return false; + } + + if (!allow_class_selector && zend_opcache_static_cache_lookup_loaded_class_key(key) != NULL) { + zend_argument_value_error(arg_num, "must not be a loaded class name"); + + return false; + } + return true; } +static zend_always_inline bool zend_opcache_static_cache_validate_key(zend_string *key, uint32_t arg_num) +{ + return zend_opcache_static_cache_validate_key_ex(key, arg_num, false); +} + +static zend_always_inline bool zend_opcache_static_cache_validate_key_or_class(zend_string *key_or_class, uint32_t arg_num) +{ + return zend_opcache_static_cache_validate_key_ex(key_or_class, arg_num, true); +} + static zend_always_inline bool zend_opcache_static_cache_validate_store_array_keys(HashTable *values, uint32_t arg_num) { zend_string *key; ZEND_HASH_FOREACH_STR_KEY(values, key) { - if (key == NULL || ZSTR_LEN(key) == 0) { - zend_argument_value_error(arg_num, "must be an array with non-empty string keys"); + if (key == NULL || ZSTR_LEN(key) == 0 || + zend_opcache_static_cache_key_has_reserved_class_prefix(key) || + zend_opcache_static_cache_lookup_loaded_class_key(key) != NULL + ) { + zend_argument_value_error(arg_num, "must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names"); return false; } @@ -187,8 +232,11 @@ static zend_always_inline bool zend_opcache_static_cache_prepare_key_list( ZEND_HASH_FOREACH_VAL(keys, value) { ZVAL_DEREF(value); if (Z_TYPE_P(value) == IS_STRING) { - if (Z_STRLEN_P(value) == 0) { - zend_argument_value_error(arg_num, "must contain only non-empty string or int cache keys"); + if (Z_STRLEN_P(value) == 0 || + zend_opcache_static_cache_key_has_reserved_class_prefix(Z_STR_P(value)) || + zend_opcache_static_cache_lookup_loaded_class_key(Z_STR_P(value)) != NULL + ) { + zend_argument_value_error(arg_num, "must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names"); zend_opcache_static_cache_release_key_list(prepared, index); return false; @@ -198,7 +246,7 @@ static zend_always_inline bool zend_opcache_static_cache_prepare_key_list( } else if (Z_TYPE_P(value) == IS_LONG) { prepared[index++] = zend_long_to_str(Z_LVAL_P(value)); } else { - zend_argument_value_error(arg_num, "must contain only non-empty string or int cache keys"); + zend_argument_value_error(arg_num, "must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names"); zend_opcache_static_cache_release_key_list(prepared, index); return false; @@ -522,6 +570,7 @@ static zend_always_inline void zend_opcache_static_cache_register_classes(void) return; } + zend_opcache_static_cache_info_ce = register_class_OPcache_StaticCacheInfo(); zend_opcache_static_cache_persistent_attribute_ce = register_class_OPcache_PersistentStatic(); zend_mark_internal_attribute(zend_opcache_static_cache_persistent_attribute_ce); zend_opcache_static_cache_strategy_ce = register_class_OPcache_CacheStrategy(); @@ -683,6 +732,17 @@ static zend_always_inline bool zend_opcache_static_cache_parse_ttl(zend_long ttl return true; } +static zend_always_inline bool zend_opcache_static_cache_parse_lease(zend_long lease, uint32_t arg_num) +{ + if (lease < 0) { + zend_argument_value_error(arg_num, "must be greater than or equal to 0"); + + return false; + } + + return true; +} + static zend_always_inline bool zend_opcache_static_cache_require_available_read(void) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); @@ -813,6 +873,27 @@ static zend_always_inline bool zend_opcache_static_cache_explicit_delete_prevali return deleted; } +static zend_always_inline bool zend_opcache_static_cache_explicit_delete_or_class_prevalidated(zend_string *key_or_class) +{ + zend_class_entry *ce; + + ce = zend_opcache_static_cache_lookup_loaded_class_key(key_or_class); + if (ce != NULL) { + if (zend_opcache_static_cache_class_has_keys(ce)) { + if (!zend_opcache_static_cache_acquire_write_lock()) { + return false; + } + + zend_opcache_static_cache_delete_class_keys_locked(ce); + zend_opcache_static_cache_unlock(); + } + + return true; + } + + return zend_opcache_static_cache_explicit_delete_prevalidated(key_or_class); +} + static zend_always_inline bool zend_opcache_static_cache_explicit_clear_prevalidated(void) { bool cleared; @@ -976,7 +1057,11 @@ static zend_always_inline zend_result zend_opcache_static_cache_exists_api(zend_ return SUCCESS; } -static zend_always_inline zend_result zend_opcache_static_cache_lock_api(zend_opcache_static_cache_context *context, zend_string *key, bool *locked) +static zend_always_inline zend_result zend_opcache_static_cache_lock_api( + zend_opcache_static_cache_context *context, + zend_string *key, + zend_long lease, + bool *locked) { zend_opcache_static_cache_context *previous_context; @@ -987,7 +1072,24 @@ static zend_always_inline zend_result zend_opcache_static_cache_lock_api(zend_op return FAILURE; } - *locked = zend_opcache_static_cache_try_acquire_entry_lock(key); + *locked = zend_opcache_static_cache_try_acquire_entry_lock(key, lease); + zend_opcache_static_cache_restore_context(previous_context); + + return SUCCESS; +} + +static zend_always_inline zend_result zend_opcache_static_cache_unlock_api(zend_opcache_static_cache_context *context, zend_string *key, bool *unlocked) +{ + zend_opcache_static_cache_context *previous_context; + + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_validate_available_write()) { + zend_opcache_static_cache_restore_context(previous_context); + + return FAILURE; + } + + *unlocked = zend_opcache_static_cache_release_entry_lock(key); zend_opcache_static_cache_restore_context(previous_context); return SUCCESS; @@ -1228,7 +1330,7 @@ void zend_opcache_static_cache_volatile_get_status(zval *return_value) { zend_opcache_static_cache_context *previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); - zend_opcache_static_cache_populate_array(return_value); + zend_opcache_static_cache_populate_info(return_value); zend_opcache_static_cache_restore_context(previous_context); } @@ -1236,7 +1338,7 @@ void zend_opcache_static_cache_persistent_get_status(zval *return_value) { zend_opcache_static_cache_context *previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); - zend_opcache_static_cache_populate_array(return_value); + zend_opcache_static_cache_populate_info(return_value); zend_opcache_static_cache_restore_context(previous_context); } @@ -1442,26 +1544,34 @@ ZEND_FUNCTION(OPcache_volatile_exists) ZEND_FUNCTION(OPcache_volatile_lock) { zend_string *key; + zend_long lease = 0; bool locked; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(lease) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { RETURN_THROWS(); } - if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_volatile_context_state, key, &locked) == FAILURE) { + if (!zend_opcache_static_cache_parse_lease(lease, 2)) { + RETURN_THROWS(); + } + + if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_volatile_context_state, key, lease, &locked) == FAILURE) { RETURN_THROWS(); } RETURN_BOOL(locked); } -ZEND_FUNCTION(OPcache_volatile_delete) +ZEND_FUNCTION(OPcache_volatile_unlock) { zend_string *key; + bool unlocked; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(key) @@ -1471,11 +1581,30 @@ ZEND_FUNCTION(OPcache_volatile_delete) RETURN_THROWS(); } + if (zend_opcache_static_cache_unlock_api(&zend_opcache_static_cache_volatile_context_state, key, &unlocked) == FAILURE) { + RETURN_THROWS(); + } + + RETURN_BOOL(unlocked); +} + +ZEND_FUNCTION(OPcache_volatile_delete) +{ + zend_string *key_or_class; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key_or_class) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key_or_class(key_or_class, 1)) { + RETURN_THROWS(); + } + if (!zend_opcache_static_cache_validate_available_write()) { RETURN_THROWS(); } - if (!zend_opcache_static_cache_explicit_delete_prevalidated(key)) { + if (!zend_opcache_static_cache_explicit_delete_or_class_prevalidated(key_or_class)) { RETURN_THROWS(); } } @@ -1541,7 +1670,7 @@ ZEND_FUNCTION(OPcache_volatile_cache_info) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); - zend_opcache_static_cache_populate_array(return_value); + zend_opcache_static_cache_populate_info(return_value); zend_opcache_static_cache_restore_context(previous_context); } @@ -1690,27 +1819,34 @@ ZEND_FUNCTION(OPcache_persistent_exists) ZEND_FUNCTION(OPcache_persistent_lock) { zend_string *key; + zend_long lease = 0; bool locked; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(lease) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { RETURN_THROWS(); } - if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_persistent_context_state, key, &locked) == FAILURE) { + if (!zend_opcache_static_cache_parse_lease(lease, 2)) { + RETURN_THROWS(); + } + + if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_persistent_context_state, key, lease, &locked) == FAILURE) { RETURN_THROWS(); } RETURN_BOOL(locked); } -ZEND_FUNCTION(OPcache_persistent_delete) +ZEND_FUNCTION(OPcache_persistent_unlock) { - zend_opcache_static_cache_context *previous_context; zend_string *key; + bool unlocked; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(key) @@ -1720,6 +1856,26 @@ ZEND_FUNCTION(OPcache_persistent_delete) RETURN_THROWS(); } + if (zend_opcache_static_cache_unlock_api(&zend_opcache_static_cache_persistent_context_state, key, &unlocked) == FAILURE) { + RETURN_THROWS(); + } + + RETURN_BOOL(unlocked); +} + +ZEND_FUNCTION(OPcache_persistent_delete) +{ + zend_opcache_static_cache_context *previous_context; + zend_string *key_or_class; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(key_or_class) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_key_or_class(key_or_class, 1)) { + RETURN_THROWS(); + } + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); if (!zend_opcache_static_cache_validate_available_write()) { zend_opcache_static_cache_restore_context(previous_context); @@ -1727,7 +1883,7 @@ ZEND_FUNCTION(OPcache_persistent_delete) RETURN_THROWS(); } - if (!zend_opcache_static_cache_explicit_delete_prevalidated(key)) { + if (!zend_opcache_static_cache_explicit_delete_or_class_prevalidated(key_or_class)) { zend_opcache_static_cache_restore_context(previous_context); RETURN_THROWS(); @@ -1877,7 +2033,7 @@ ZEND_FUNCTION(OPcache_persistent_atomic_decrement) RETURN_THROWS(); } - if (!zend_opcache_static_cache_atomic_update_locked(key, step, true, false, &new_value, "Atomic decrement requires an integer value")) { + if (!zend_opcache_static_cache_atomic_update_locked(key, step, true, true, &new_value, "Atomic decrement requires an integer value")) { zend_opcache_static_cache_unlock(); if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); @@ -1906,6 +2062,6 @@ ZEND_FUNCTION(OPcache_persistent_cache_info) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); - zend_opcache_static_cache_populate_array(return_value); + zend_opcache_static_cache_populate_info(return_value); zend_opcache_static_cache_restore_context(previous_context); } diff --git a/ext/opcache/zend_static_cache_entries.c b/ext/opcache/zend_static_cache_entries.c index 743938da518a..e63ea3fe0025 100644 --- a/ext/opcache/zend_static_cache_entries.c +++ b/ext/opcache/zend_static_cache_entries.c @@ -1162,6 +1162,7 @@ bool zend_opcache_static_cache_clear_locked(void) } memset(entries, 0, sizeof(zend_opcache_static_cache_entry) * header->capacity); + memset(header->entry_lock_leases, 0, sizeof(header->entry_lock_leases)); header->count = 0; header->mutation_epoch = mutation_epoch + 1; @@ -1992,9 +1993,9 @@ bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long if (!zend_opcache_static_cache_find_slot_for_write_locked(key, hash, &header, &slot_index, &found) || !found) { if (insert_if_missing) { - ZVAL_LONG(&initial_value, step); + ZVAL_LONG(&initial_value, decrement ? -step : step); if (zend_opcache_static_cache_store_locked(key, &initial_value, 0, true)) { - *new_value = step; + *new_value = Z_LVAL(initial_value); return true; } diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index 0456aaf0fb23..6ed4945dd23f 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -44,7 +44,7 @@ #include "SAPI.h" #define ZEND_OPCACHE_STATIC_CACHE_MAGIC 0xCAC17E01U -#define ZEND_OPCACHE_STATIC_CACHE_VERSION 1U +#define ZEND_OPCACHE_STATIC_CACHE_VERSION 2U #define ZEND_OPCACHE_STATIC_CACHE_MIN_CAPACITY 127U #define ZEND_OPCACHE_STATIC_CACHE_MAX_CAPACITY 65521U #define ZEND_OPCACHE_STATIC_CACHE_SLOT_BYTES 256U @@ -158,6 +158,10 @@ typedef struct _zend_opcache_static_cache_context { bool strict_store_failure; } zend_opcache_static_cache_context; +typedef struct _zend_opcache_static_cache_entry_lock_lease { + uint64_t expires_at; +} zend_opcache_static_cache_entry_lock_lease; + /* The same storage primitives serve the explicit volatile cache, VolatileStatic, and * PersistentStatic. Callers switch this TLS context around short critical sections * so allocator, lookup-cache, and lock helpers always operate on the right SHM @@ -173,6 +177,7 @@ typedef struct _zend_opcache_static_cache_header { uint32_t free_list; uint32_t last_block_offset; uint64_t mutation_epoch; + zend_opcache_static_cache_entry_lock_lease entry_lock_leases[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; #ifndef ZEND_WIN32 uint32_t reserved_lock; #endif @@ -294,6 +299,7 @@ typedef struct _zend_opcache_static_cache_shared_graph_ref { typedef struct _zend_opcache_static_cache_entry_lock { zend_opcache_static_cache_context *context; zend_ulong owner_pid; + zend_long lease; uint32_t stripe; } zend_opcache_static_cache_entry_lock; @@ -356,6 +362,7 @@ typedef struct _zend_opcache_static_cache_shared_graph_copy_context { extern zend_class_entry *zend_opcache_static_cache_exception_ce; extern zend_class_entry *zend_opcache_static_cache_strategy_ce; +extern zend_class_entry *zend_opcache_static_cache_info_ce; extern zend_opcache_static_cache_context zend_opcache_static_cache_volatile_context_state; extern zend_opcache_static_cache_context zend_opcache_static_cache_persistent_context_state; extern bool zend_opcache_static_cache_subsystem_disabled; @@ -417,14 +424,14 @@ bool zend_opcache_static_cache_compact_to_fit_locked(size_t size); bool zend_opcache_static_cache_startup_storage_before_request(void); void zend_opcache_static_cache_shutdown_storage(void); void zend_opcache_static_cache_ensure_ready(void); -void zend_opcache_static_cache_populate_array(zval *return_value); +void zend_opcache_static_cache_populate_info(zval *return_value); bool zend_opcache_static_cache_rlock(void); bool zend_opcache_static_cache_wlock(void); void zend_opcache_static_cache_unlock(void); bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key); -bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key); +bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key, zend_long lease); bool zend_opcache_static_cache_has_entry_lock(zend_string *key); -void zend_opcache_static_cache_release_entry_lock(zend_string *key); +bool zend_opcache_static_cache_release_entry_lock(zend_string *key); bool zend_opcache_static_cache_has_all_entry_locks(void); void zend_opcache_static_cache_release_active_entry_locks(void); void zend_opcache_static_cache_release_request_entry_locks(void); @@ -484,6 +491,8 @@ void zend_opcache_static_cache_release_request_local_slots(void); void zend_opcache_static_cache_update_mutation_hook_state(void); bool zend_opcache_static_cache_prepare_memo_fetch(zval *value, zend_opcache_static_cache_prepared_value *prepared); void zend_opcache_static_cache_prepare_memo_store(zval *value, zend_opcache_static_cache_prepared_value *prepared); +bool zend_opcache_static_cache_class_has_keys(zend_class_entry *ce); +void zend_opcache_static_cache_delete_class_keys_locked(zend_class_entry *ce); void zend_opcache_static_cache_delete_script_keys_locked(zend_persistent_script *persistent_script); void zend_opcache_static_cache_register_hooks(void); void zend_opcache_static_cache_unregister_hooks(void); diff --git a/ext/opcache/zend_static_cache_statics.c b/ext/opcache/zend_static_cache_statics.c index 8366d580eb20..97772ee7a582 100644 --- a/ext/opcache/zend_static_cache_statics.c +++ b/ext/opcache/zend_static_cache_statics.c @@ -2540,6 +2540,61 @@ static zend_opcache_static_cache_static_slot_handle *zend_opcache_static_cache_t return NULL; } +static bool zend_opcache_static_cache_method_has_keys(zend_class_entry *ce) +{ + zend_opcache_static_cache_kind kind; + zend_function *function; + zend_op_array *op_array; + + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, function) { + if (function == NULL || function->type != ZEND_USER_FUNCTION) { + continue; + } + + op_array = &function->op_array; + kind = zend_opcache_static_cache_op_array_kind(op_array); + if (kind != ZEND_OPCACHE_STATIC_CACHE_NONE && + zend_opcache_static_cache_context_for_kind(kind) == zend_opcache_static_cache_active_context() + ) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return false; +} + +bool zend_opcache_static_cache_class_has_keys(zend_class_entry *ce) +{ + zend_opcache_static_cache_kind kind; + zend_string *property_name; + zend_property_info *property_info; + + if (ce == NULL || ce->name == NULL) { + return false; + } + + if (zend_opcache_static_cache_class_blob_enabled(ce)) { + kind = zend_opcache_static_cache_class_blob_kind(ce); + + return zend_opcache_static_cache_context_for_kind(kind) == zend_opcache_static_cache_active_context(); + } + + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, property_name, property_info) { + if (property_name == NULL) { + continue; + } + + kind = zend_opcache_static_cache_static_property_kind(ce, property_info); + if (kind != ZEND_OPCACHE_STATIC_CACHE_NONE && + zend_opcache_static_cache_context_for_kind(kind) == zend_opcache_static_cache_active_context() + ) { + return true; + } + } ZEND_HASH_FOREACH_END(); + + return zend_opcache_static_cache_method_has_keys(ce); +} + static void zend_opcache_static_cache_delete_method_keys_locked(zend_class_entry *ce) { zend_opcache_static_cache_kind kind; @@ -2579,7 +2634,7 @@ static void zend_opcache_static_cache_delete_method_keys_locked(zend_class_entry } ZEND_HASH_FOREACH_END(); } -static void zend_opcache_static_cache_delete_class_keys_locked(zend_class_entry *ce) +void zend_opcache_static_cache_delete_class_keys_locked(zend_class_entry *ce) { zend_opcache_static_cache_kind kind; zend_string *property_name, *cache_key; diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 4b824c25a0b8..bc8c507eceae 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -935,12 +935,194 @@ static void zend_opcache_static_cache_entry_locks_reinit_after_fork(zend_opcache } #endif -static void zend_opcache_static_cache_release_entry_lock_context(HashTable **locks_ptr, uint32_t *counts) +typedef enum _zend_opcache_static_cache_entry_lock_release_mode { + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP, + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_CLEAR, + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_PRESERVE_LEASES +} zend_opcache_static_cache_entry_lock_release_mode; + +static void zend_opcache_static_cache_entry_lock_lease_wait(void) +{ +#ifdef ZEND_WIN32 + Sleep(10); +#elif defined(HAVE_UNISTD_H) + usleep(10000); +#endif +} + +static uint64_t zend_opcache_static_cache_entry_lock_expires_at(zend_long lease) +{ + uint64_t now, lease_seconds; + + ZEND_ASSERT(lease > 0); + + now = (uint64_t) time(NULL); + lease_seconds = (uint64_t) lease; + if (lease_seconds > UINT64_MAX - now) { + return UINT64_MAX; + } + + return now + lease_seconds; +} + +static bool zend_opcache_static_cache_update_entry_lock_lease( + zend_opcache_static_cache_context *context, + uint32_t stripe, + zend_long lease) +{ + zend_opcache_static_cache_context *previous_context; + zend_opcache_static_cache_header *header; + uint64_t expires_at; + bool result = false; + + if (lease <= 0) { + return true; + } + + expires_at = zend_opcache_static_cache_entry_lock_expires_at(lease); + previous_context = zend_opcache_static_cache_activate_context(context); + if (zend_opcache_static_cache_wlock()) { + header = zend_opcache_static_cache_header_ptr(); + if (zend_opcache_static_cache_header_init_locked()) { + if (header->entry_lock_leases[stripe].expires_at < expires_at) { + header->entry_lock_leases[stripe].expires_at = expires_at; + } + result = true; + } + zend_opcache_static_cache_unlock(); + } + zend_opcache_static_cache_restore_context(previous_context); + + return result; +} + +static void zend_opcache_static_cache_clear_entry_lock_lease( + zend_opcache_static_cache_context *context, + uint32_t stripe) +{ + zend_opcache_static_cache_context *previous_context; + zend_opcache_static_cache_header *header; + + previous_context = zend_opcache_static_cache_activate_context(context); + if (zend_opcache_static_cache_wlock()) { + header = zend_opcache_static_cache_header_ptr(); + if (zend_opcache_static_cache_header_is_initialized_locked()) { + header->entry_lock_leases[stripe].expires_at = 0; + } + zend_opcache_static_cache_unlock(); + } + zend_opcache_static_cache_restore_context(previous_context); +} + +static bool zend_opcache_static_cache_entry_lock_lease_allows_acquire( + zend_opcache_static_cache_context *context, + uint32_t stripe, + bool blocking) +{ + zend_opcache_static_cache_context *previous_context; + zend_opcache_static_cache_header *header; + uint32_t *counts = zend_opcache_static_cache_entry_lock_counts_for_context(context); + uint64_t expires_at, now; + bool active; + + if (counts[stripe] != 0) { + return true; + } + + for (;;) { + active = false; + previous_context = zend_opcache_static_cache_activate_context(context); + if (!zend_opcache_static_cache_wlock()) { + zend_opcache_static_cache_restore_context(previous_context); + + return false; + } + + header = zend_opcache_static_cache_header_ptr(); + if (zend_opcache_static_cache_header_is_initialized_locked()) { + expires_at = header->entry_lock_leases[stripe].expires_at; + if (expires_at != 0) { + now = (uint64_t) time(NULL); + if (expires_at <= now) { + header->entry_lock_leases[stripe].expires_at = 0; + } else { + active = true; + } + } + } + + zend_opcache_static_cache_unlock(); + zend_opcache_static_cache_restore_context(previous_context); + + if (!active) { + return true; + } + + if (!blocking) { + return false; + } + + zend_opcache_static_cache_entry_lock_lease_wait(); + } +} + +static void zend_opcache_static_cache_prepare_entry_lock_leases_for_release( + zend_opcache_static_cache_context *context, + HashTable *locks, + uint32_t *counts, + zend_opcache_static_cache_entry_lock_release_mode mode) +{ + zend_opcache_static_cache_context *previous_context; + zend_opcache_static_cache_entry_lock *lock; + zend_opcache_static_cache_header *header; + zend_long leases[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES] = {0}; + uint32_t stripe; + + if (locks == NULL || mode == ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP) { + return; + } + + if (mode == ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_PRESERVE_LEASES) { + ZEND_HASH_FOREACH_PTR(locks, lock) { + if (lock != NULL && lock->lease > leases[lock->stripe]) { + leases[lock->stripe] = lock->lease; + } + } ZEND_HASH_FOREACH_END(); + } + + previous_context = zend_opcache_static_cache_activate_context(context); + if (zend_opcache_static_cache_wlock()) { + header = zend_opcache_static_cache_header_ptr(); + if (zend_opcache_static_cache_header_init_locked()) { + for (stripe = 0; stripe < ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES; stripe++) { + if (counts[stripe] == 0) { + continue; + } + + if (leases[stripe] > 0) { + header->entry_lock_leases[stripe].expires_at = + zend_opcache_static_cache_entry_lock_expires_at(leases[stripe]); + } else { + header->entry_lock_leases[stripe].expires_at = 0; + } + } + } + zend_opcache_static_cache_unlock(); + } + zend_opcache_static_cache_restore_context(previous_context); +} + +static void zend_opcache_static_cache_release_entry_lock_context( + zend_opcache_static_cache_context *context, + HashTable **locks_ptr, + uint32_t *counts, + zend_opcache_static_cache_entry_lock_release_mode mode) { if (*locks_ptr == NULL) { return; } + zend_opcache_static_cache_prepare_entry_lock_leases_for_release(context, *locks_ptr, counts, mode); zend_hash_destroy(*locks_ptr); FREE_HASHTABLE(*locks_ptr); *locks_ptr = NULL; @@ -965,11 +1147,15 @@ static void zend_opcache_static_cache_ensure_entry_lock_process(void) /* The request-local lock tables were inherited across fork. Destroy the * child copies without unlocking stripes owned by the parent process. */ zend_opcache_static_cache_release_entry_lock_context( + &zend_opcache_static_cache_volatile_context_state, &zend_opcache_static_cache_volatile_entry_locks, - zend_opcache_static_cache_volatile_entry_lock_counts); + zend_opcache_static_cache_volatile_entry_lock_counts, + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP); zend_opcache_static_cache_release_entry_lock_context( + &zend_opcache_static_cache_persistent_context_state, &zend_opcache_static_cache_persistent_entry_locks, - zend_opcache_static_cache_persistent_entry_lock_counts); + zend_opcache_static_cache_persistent_entry_lock_counts, + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP); #ifdef ZTS zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_volatile_context_state.storage); zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_persistent_context_state.storage); @@ -1948,7 +2134,7 @@ void zend_opcache_static_cache_ensure_ready(void) zend_opcache_static_cache_set_available(); } -void zend_opcache_static_cache_populate_array(zval *return_value) +void zend_opcache_static_cache_populate_info(zval *return_value) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); @@ -1956,7 +2142,7 @@ void zend_opcache_static_cache_populate_array(zval *return_value) zend_opcache_static_cache_header *header; zend_long entry_count = 0; - array_init(return_value); + object_init_ex(return_value, zend_opcache_static_cache_info_ce); if (runtime->available && zend_opcache_static_cache_rlock()) { header = zend_opcache_static_cache_header_ptr(); @@ -1967,18 +2153,19 @@ void zend_opcache_static_cache_populate_array(zval *return_value) zend_opcache_static_cache_unlock(); } - add_assoc_bool(return_value, "enabled", runtime->enabled); - add_assoc_bool(return_value, "available", runtime->available); - add_assoc_bool(return_value, "startup_failed", runtime->startup_failed); - add_assoc_bool(return_value, "backend_initialized", runtime->backend_initialized); - add_assoc_long(return_value, "configured_memory", (zend_long) runtime->configured_memory); - add_assoc_long(return_value, "shared_memory", (zend_long) storage->size); - add_assoc_long(return_value, "entry_count", entry_count); - add_assoc_long(return_value, "segment_count", storage->segment_count); - add_assoc_string(return_value, "shared_model", storage->model ? (char *) storage->model : ""); - + zend_update_property_bool(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("enabled"), runtime->enabled); + zend_update_property_bool(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("available"), runtime->available); + zend_update_property_bool(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("startup_failed"), runtime->startup_failed); + zend_update_property_bool(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("backend_initialized"), runtime->backend_initialized); + zend_update_property_long(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("configured_memory"), (zend_long) runtime->configured_memory); + zend_update_property_long(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("shared_memory"), (zend_long) storage->size); + zend_update_property_long(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("entry_count"), entry_count); + zend_update_property_long(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("segment_count"), storage->segment_count); + zend_update_property_string(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("shared_model"), storage->model ? (char *) storage->model : ""); if (runtime->failure_reason) { - add_assoc_string(return_value, "failure_reason", (char *) runtime->failure_reason); + zend_update_property_string(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("failure_reason"), (char *) runtime->failure_reason); + } else { + zend_update_property_null(zend_opcache_static_cache_info_ce, Z_OBJ_P(return_value), ZEND_STRL("failure_reason")); } } @@ -2010,6 +2197,12 @@ bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key) return true; } + if (!zend_opcache_static_cache_entry_lock_lease_allows_acquire(context, stripe, true)) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); + + return false; + } + if (!zend_opcache_static_cache_lock_entry_stripe(context, stripe)) { zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); @@ -2024,6 +2217,7 @@ bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key) #else 0; #endif + lock->lease = 0; lock->stripe = stripe; if (zend_hash_add_ptr(zend_opcache_static_cache_entry_locks(context), key, lock) == NULL) { @@ -2036,7 +2230,7 @@ bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key) return true; } -bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key) +bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key, zend_long lease) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); zend_opcache_static_cache_entry_lock *lock; @@ -2045,10 +2239,21 @@ bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key) zend_opcache_static_cache_ensure_entry_lock_process(); - if (*locks_ptr != NULL && zend_hash_exists(*locks_ptr, key)) { + if (*locks_ptr != NULL && (lock = zend_hash_find_ptr(*locks_ptr, key)) != NULL) { + if (lease > lock->lease) { + if (!zend_opcache_static_cache_update_entry_lock_lease(context, stripe, lease)) { + return false; + } + lock->lease = lease; + } + return true; } + if (!zend_opcache_static_cache_entry_lock_lease_allows_acquire(context, stripe, false)) { + return false; + } + if (!zend_opcache_static_cache_try_lock_entry_stripe(context, stripe)) { return false; } @@ -2061,6 +2266,7 @@ bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key) #else 0; #endif + lock->lease = lease; lock->stripe = stripe; if (zend_hash_add_ptr(zend_opcache_static_cache_entry_locks(context), key, lock) == NULL) { @@ -2070,6 +2276,12 @@ bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key) return true; } + if (!zend_opcache_static_cache_update_entry_lock_lease(context, stripe, lease)) { + zend_hash_del(*locks_ptr, key); + + return false; + } + return true; } @@ -2082,17 +2294,30 @@ bool zend_opcache_static_cache_has_entry_lock(zend_string *key) return *locks_ptr != NULL && zend_hash_exists(*locks_ptr, key); } -void zend_opcache_static_cache_release_entry_lock(zend_string *key) +bool zend_opcache_static_cache_release_entry_lock(zend_string *key) { - HashTable **locks_ptr = zend_opcache_static_cache_entry_locks_ptr_for_context(zend_opcache_static_cache_active_context()); + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_entry_lock *lock; + HashTable **locks_ptr = zend_opcache_static_cache_entry_locks_ptr_for_context(context); + uint32_t *counts = zend_opcache_static_cache_entry_lock_counts_for_context(context); zend_opcache_static_cache_ensure_entry_lock_process(); if (*locks_ptr == NULL) { - return; + return false; } + lock = zend_hash_find_ptr(*locks_ptr, key); + if (lock == NULL) { + return false; + } + + if (counts[lock->stripe] == 1) { + zend_opcache_static_cache_clear_entry_lock_lease(context, lock->stripe); + } zend_hash_del(*locks_ptr, key); + + return true; } bool zend_opcache_static_cache_has_all_entry_locks(void) @@ -2115,19 +2340,25 @@ void zend_opcache_static_cache_release_active_entry_locks(void) zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); zend_opcache_static_cache_release_entry_lock_context( + context, zend_opcache_static_cache_entry_locks_ptr_for_context(context), - zend_opcache_static_cache_entry_lock_counts_for_context(context)); + zend_opcache_static_cache_entry_lock_counts_for_context(context), + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_CLEAR); } void zend_opcache_static_cache_release_request_entry_locks(void) { zend_opcache_static_cache_release_entry_lock_context( + &zend_opcache_static_cache_volatile_context_state, &zend_opcache_static_cache_volatile_entry_locks, - zend_opcache_static_cache_volatile_entry_lock_counts + zend_opcache_static_cache_volatile_entry_lock_counts, + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_PRESERVE_LEASES ); zend_opcache_static_cache_release_entry_lock_context( + &zend_opcache_static_cache_persistent_context_state, &zend_opcache_static_cache_persistent_entry_locks, - zend_opcache_static_cache_persistent_entry_lock_counts + zend_opcache_static_cache_persistent_entry_lock_counts, + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_PRESERVE_LEASES ); #if !defined(ZEND_WIN32) && defined(ZTS) if (zend_opcache_static_cache_entry_locks_process_is_fork_child) { From a492b8b34bf206160187c0d58511a1cf8687298b Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 03:19:17 +0000 Subject: [PATCH 14/29] fix --- Zend/Optimizer/zend_func_infos.h | 2 - ext/opcache/zend_static_cache_statics.c | 76 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 90cdabf70094..5937d349451f 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -289,9 +289,7 @@ static const func_info_t func_infos[] = { F1("opcache_get_status", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), F1("opcache_get_configuration", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), FN("OPcache\\volatile_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), - FN("OPcache\\volatile_cache_info", MAY_BE_OBJECT), FN("OPcache\\persistent_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), - FN("OPcache\\persistent_cache_info", MAY_BE_OBJECT), F1("openssl_x509_parse", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_csr_get_subject", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_pkey_get_details", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), diff --git a/ext/opcache/zend_static_cache_statics.c b/ext/opcache/zend_static_cache_statics.c index 97772ee7a582..fbb09b294907 100644 --- a/ext/opcache/zend_static_cache_statics.c +++ b/ext/opcache/zend_static_cache_statics.c @@ -2970,6 +2970,78 @@ static void zend_opcache_static_cache_publish_context(zend_opcache_static_cache_ zend_opcache_static_cache_restore_context(previous_context); } +static void zend_opcache_static_cache_capture_decoded_reachable_value_ex( + zval *value, + HashTable *seen_arrays, + HashTable *seen_objects +); + +static void zend_opcache_static_cache_capture_decoded_hash_values( + HashTable *values, + HashTable *seen_arrays, + HashTable *seen_objects +) +{ + zval *child, *source_value; + + if (values == NULL) { + return; + } + + ZEND_HASH_FOREACH_VAL(values, child) { + source_value = Z_TYPE_P(child) == IS_INDIRECT ? Z_INDIRECT_P(child) : child; + if (Z_TYPE_P(source_value) != IS_UNDEF) { + zend_opcache_static_cache_capture_decoded_reachable_value_ex(source_value, seen_arrays, seen_objects); + } + } ZEND_HASH_FOREACH_END(); +} + +static bool zend_opcache_static_cache_capture_decoded_safe_direct_object( + zval *value, + HashTable *seen_arrays, + HashTable *seen_objects +) +{ + zend_class_entry *ce, *base_ce = NULL; + zend_opcache_static_cache_safe_direct_state_serialize_func_t serialize_func; + zval state; + HashTable *properties; + + ce = Z_OBJCE_P(value); + base_ce = zend_opcache_serializer_find_safe_direct_cache_base(ce); + if (base_ce == NULL || + zend_opcache_serializer_has_safe_direct_cache_overrides(ce, base_ce) + ) { + return false; + } + + serialize_func = zend_opcache_static_cache_safe_direct_state_serialize_func(ce); + if (serialize_func == NULL) { + return false; + } + + /* Internal safe-direct objects may reject ZEND_PROP_PURPOSE_SERIALIZE + * because their cache state is exposed through explicit handlers. */ + ZVAL_UNDEF(&state); + if (serialize_func(value, &state) && Z_TYPE(state) == IS_ARRAY) { + zend_opcache_static_cache_capture_decoded_hash_values(Z_ARRVAL(state), seen_arrays, seen_objects); + } + if (!Z_ISUNDEF(state)) { + zval_ptr_dtor(&state); + } + + if (EG(exception)) { + return true; + } + + if (!zend_opcache_static_cache_safe_direct_state_includes_properties(ce)) { + properties = zend_std_get_properties(Z_OBJ_P(value)); + zend_opcache_static_cache_capture_decoded_hash_values(properties, seen_arrays, seen_objects); + } + + return true; +} + static void zend_opcache_static_cache_capture_decoded_reachable_value_ex( zval *value, HashTable *seen_arrays, @@ -3026,6 +3098,10 @@ static void zend_opcache_static_cache_capture_decoded_reachable_value_ex( return; } + if (zend_opcache_static_cache_capture_decoded_safe_direct_object(value, seen_arrays, seen_objects)) { + return; + } + properties = zend_get_properties_for(value, ZEND_PROP_PURPOSE_SERIALIZE); if (properties == NULL) { return; From d03aa4f58321add0121ee62f0ec0c8c8efa46172 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 03:39:02 +0000 Subject: [PATCH 15/29] Rename OPcache static cache persistent backend to pinned --- ext/opcache/ZendAccelerator.c | 2 +- ext/opcache/ZendAccelerator.h | 2 +- ext/opcache/opcache.stub.php | 28 ++-- ext/opcache/opcache_arginfo.h | 92 ++++++------- ...s_blob_dynamic_method_statics_jit_001.phpt | 22 ++-- ...ent_static_class_blob_persistence_001.phpt | 8 +- ..._static_complex_value_persistence_001.phpt | 6 +- ...static_object_assignment_snapshot_001.phpt | 12 +- ...fpm_persistent_static_persistence_001.phpt | 8 +- ...ent_static_property_array_publish_001.phpt | 10 +- ...pm_persistent_static_scope_writes_001.phpt | 10 +- ...e_persistent_cache_shared_workers_001.phpt | 16 +-- ...licit_cache_store_delete_zts_threads_001.c | 22 ++-- ..._001.c => pinned_static_zts_threads_001.c} | 10 +- ....inc => pinned_static_zts_threads_001.inc} | 18 +-- ...atic_cache_attribute_class_delete_001.phpt | 78 +++++------ ..._cache_attribute_exact_key_delete_001.phpt | 58 ++++----- ...ute_publish_userland_outside_lock_001.phpt | 16 +-- ...static_cache_attribute_signatures_001.phpt | 2 +- ...abled_without_static_cache_memory_001.phpt | 2 +- ...ache_explicit_cache_complex_store_001.phpt | 12 +- ..._destructive_mutators_no_deadlock_001.phpt | 22 ++-- ..._cache_explicit_cache_fetch_array_001.phpt | 14 +- ...cache_fetch_userland_outside_lock_001.phpt | 18 +-- ...it_cache_fragmentation_relocation_001.phpt | 16 +-- ...che_fragmentation_relocation_skip_001.phpt | 14 +- ...che_explicit_cache_key_validation_001.phpt | 100 +++++++------- ...e_explicit_cache_lock_clear_reset_001.phpt | 18 +-- ...ic_cache_explicit_cache_lock_fork_001.phpt | 16 +-- ...c_cache_explicit_cache_lock_lease_001.phpt | 12 +- ...ck_parent_survives_child_shutdown_001.phpt | 12 +- ...t_cache_lock_public_mutators_fork_001.phpt | 26 ++-- ...che_explicit_cache_lock_rshutdown_001.phpt | 14 +- ...c_cache_explicit_cache_lock_store_001.phpt | 16 +-- ...licit_cache_lock_stripe_collision_001.phpt | 12 +- ...cache_prepare_nested_object_reuse_001.phpt | 26 ++-- ...ache_prepare_safe_direct_mutation_001.phpt | 18 +-- ...licit_cache_prepare_scratch_reuse_001.phpt | 24 ++-- ...t_cache_request_local_object_copy_001.phpt | 28 ++-- ...he_request_local_safe_direct_slot_001.phpt | 40 +++--- ...explicit_cache_request_local_slot_001.phpt | 36 +++--- ...explicit_cache_shared_graph_array_001.phpt | 6 +- ...c_cache_explicit_cache_signatures_001.phpt | 32 ++--- ...cit_cache_store_array_invalid_key_001.phpt | 8 +- ...cit_cache_store_delete_fork_reuse_001.phpt | 16 +-- ..._cache_store_delete_request_reuse_001.phpt | 16 +-- ...it_cache_store_delete_zts_threads_001.phpt | 2 +- ...tatic_cache_explicit_cache_unlock_001.phpt | 16 +-- ...static_cache_persistent_cache_api_001.phpt | 28 ++-- ...ent_cache_array_mutation_overflow_001.phpt | 14 +- ...ent_cache_atomic_increment_create_001.phpt | 32 ++--- ...rsistent_cache_atomic_non_integer_001.phpt | 10 +- ...che_persistent_cache_batch_atomic_001.phpt | 20 +-- ...atic_cache_persistent_cache_clear_001.phpt | 24 ++-- ...e_persistent_cache_clear_combined_001.phpt | 46 +++---- ...tatic_cache_persistent_cache_info_001.phpt | 26 ++-- ...stent_cache_lock_atomic_increment_001.phpt | 20 +-- ...c_cache_persistent_cache_overflow_001.phpt | 12 +- ...e_persistent_cache_store_overflow_001.phpt | 12 +- ...t_static_array_mutation_fast_path_001.phpt | 48 +++---- ...sistent_static_array_mutation_jit_001.phpt | 40 +++--- ...static_attribute_scope_separation_001.phpt | 24 ++-- ...he_persistent_static_capture_miss_001.phpt | 30 ++--- ...ersistent_static_capture_tracking_001.phpt | 50 +++---- ...s_blob_dynamic_method_statics_jit_001.phpt | 34 ++--- ...t_static_class_blob_readonly_skip_001.phpt | 26 ++-- ...tent_static_class_blob_single_key_001.phpt | 16 +-- ...static_class_property_persistence_001.phpt | 16 +-- ..._static_complex_value_persistence_001.phpt | 14 +- ...d_without_persistent_cache_memory_001.phpt | 14 +- ...stent_static_inherited_attributes_001.phpt | 10 +- ...ache_persistent_static_invalidate_001.phpt | 50 +++---- ...nt_static_invalidate_after_access_001.phpt | 18 +-- ...local_copy_array_mutation_publish_001.phpt | 40 +++--- ..._static_method_static_persistence_001.phpt | 24 ++-- ...tatic_object_assignment_fast_path_001.phpt | 44 +++---- ...static_object_assignment_snapshot_001.phpt | 12 +- ...istent_static_object_dim_mutation_001.phpt | 34 ++--- ...t_static_object_property_mutation_001.phpt | 58 ++++----- ...c_property_array_mutation_publish_001.phpt | 16 +-- ...ersistent_static_readonly_publish_001.phpt | 54 ++++---- ...c_recursive_shared_graph_snapshot_001.phpt | 16 +-- ...tic_cache_persistent_static_reset_001.phpt | 36 +++--- ...sistent_static_reset_after_access_001.phpt | 18 +-- ...nt_static_store_failure_exception_001.phpt | 44 +++---- ...stent_static_timestamp_invalidate_001.phpt | 12 +- ...che_persistent_static_zts_threads_001.phpt | 16 +-- .../tests/static_cache_preload_001.inc | 2 +- .../tests/static_cache_preload_001.phpt | 2 +- .../static_cache_startup_failure_001.phpt | 20 +-- ...ache_direct_cache_safe_unstorable_001.phpt | 8 +- ...ic_cache_volatile_cache_no_atomic_001.phpt | 4 +- ...he_volatile_cache_storable_values_001.phpt | 38 +++--- ...r_isolated_from_persistent_static_001.phpt | 6 +- ...ic_cache_volatile_static_strategy_001.phpt | 6 +- .../static_cache_windows_backend_001.phpt | 20 +-- ext/opcache/zend_accelerator_module.c | 36 +++--- ext/opcache/zend_static_cache.c | 122 +++++++++--------- ext/opcache/zend_static_cache.h | 8 +- ext/opcache/zend_static_cache_entries.c | 2 +- ext/opcache/zend_static_cache_internal.h | 46 +++---- ext/opcache/zend_static_cache_statics.c | 30 ++--- ext/opcache/zend_static_cache_storage.c | 28 ++-- 103 files changed, 1224 insertions(+), 1224 deletions(-) rename ext/opcache/tests/helpers/{persistent_static_zts_threads_001.c => pinned_static_zts_threads_001.c} (96%) rename ext/opcache/tests/helpers/{persistent_static_zts_threads_001.inc => pinned_static_zts_threads_001.inc} (81%) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index ed1496582e90..f8ef072d8101 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3544,7 +3544,7 @@ static zend_result accel_post_startup(void) /* Initialize static cache if configured */ bool static_cache_configured = ZCG(accel_directives).static_cache_volatile_size_mb != 0 || - ZCG(accel_directives).static_cache_persistent_size_mb != 0 + ZCG(accel_directives).static_cache_pinned_size_mb != 0 ; bool static_cache_preload_configured = static_cache_configured && ZCG(accel_directives).preload && diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index e08fa53c4f29..58a075fe64a7 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -151,7 +151,7 @@ void zend_accel_register_static_cache_handlers(const zend_opcache_static_cache_a typedef struct _zend_accel_directives { zend_long memory_consumption; zend_long static_cache_volatile_size_mb; - zend_long static_cache_persistent_size_mb; + zend_long static_cache_pinned_size_mb; zend_long max_accelerated_files; double max_wasted_percentage; char *user_blacklist_filename; diff --git a/ext/opcache/opcache.stub.php b/ext/opcache/opcache.stub.php index 78fc79b56928..ea7bb069d9e4 100644 --- a/ext/opcache/opcache.stub.php +++ b/ext/opcache/opcache.stub.php @@ -63,7 +63,7 @@ private function __construct() {} } #[\Attribute(13)] /* TARGET_CLASS | TARGET_METHOD | TARGET_PROPERTY */ -final class PersistentStatic +final class PinnedStatic { } @@ -113,33 +113,33 @@ function volatile_clear(): void {} function volatile_cache_info(): StaticCacheInfo {} -function persistent_store(string $key, null|bool|int|float|string|array|object $value): void {} +function pinned_store(string $key, null|bool|int|float|string|array|object $value): void {} -function persistent_store_array(array $values): void {} +function pinned_store_array(array $values): void {} -function persistent_fetch(string $key, null|bool|int|float|string|array|object $default = null): null|bool|int|float|string|array|object {} +function pinned_fetch(string $key, null|bool|int|float|string|array|object $default = null): null|bool|int|float|string|array|object {} /** * @return array|null */ -function persistent_fetch_array(array $keys, ?array $default = null): ?array {} +function pinned_fetch_array(array $keys, ?array $default = null): ?array {} -function persistent_exists(string $key): bool {} +function pinned_exists(string $key): bool {} -function persistent_lock(string $key, int $lease = 0): bool {} +function pinned_lock(string $key, int $lease = 0): bool {} -function persistent_unlock(string $key): bool {} +function pinned_unlock(string $key): bool {} -function persistent_delete(string $key_or_class): void {} +function pinned_delete(string $key_or_class): void {} -function persistent_delete_array(array $keys): void {} +function pinned_delete_array(array $keys): void {} -function persistent_clear(): void {} +function pinned_clear(): void {} -function persistent_atomic_increment(string $key, int $step = 1): int {} +function pinned_atomic_increment(string $key, int $step = 1): int {} -function persistent_atomic_decrement(string $key, int $step = 1): int {} +function pinned_atomic_decrement(string $key, int $step = 1): int {} -function persistent_cache_info(): StaticCacheInfo {} +function pinned_cache_info(): StaticCacheInfo {} } diff --git a/ext/opcache/opcache_arginfo.h b/ext/opcache/opcache_arginfo.h index ca9f9288340a..939ac39c2d1e 100644 --- a/ext/opcache/opcache_arginfo.h +++ b/ext/opcache/opcache_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit opcache.stub.php instead. - * Stub hash: 2ebf95d1f9450cd5600aff1b40d93c8ad89bb108 */ + * Stub hash: 4632e9e17247948c4af8fb0c301112d78bc30c32 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_reset, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -74,39 +74,39 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_OPcache_volatile_cache_info, 0, 0, OPcache\\StaticCacheInfo, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_persistent_store, 0, 2, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_store, 0, 2, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_MASK(0, value, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_persistent_store_array, 0, 1, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_store_array, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0) ZEND_END_ARG_INFO() -#define arginfo_OPcache_persistent_fetch arginfo_OPcache_volatile_fetch +#define arginfo_OPcache_pinned_fetch arginfo_OPcache_volatile_fetch -#define arginfo_OPcache_persistent_fetch_array arginfo_OPcache_volatile_fetch_array +#define arginfo_OPcache_pinned_fetch_array arginfo_OPcache_volatile_fetch_array -#define arginfo_OPcache_persistent_exists arginfo_OPcache_volatile_exists +#define arginfo_OPcache_pinned_exists arginfo_OPcache_volatile_exists -#define arginfo_OPcache_persistent_lock arginfo_OPcache_volatile_lock +#define arginfo_OPcache_pinned_lock arginfo_OPcache_volatile_lock -#define arginfo_OPcache_persistent_unlock arginfo_OPcache_volatile_unlock +#define arginfo_OPcache_pinned_unlock arginfo_OPcache_volatile_exists -#define arginfo_OPcache_persistent_delete arginfo_OPcache_volatile_delete +#define arginfo_OPcache_pinned_delete arginfo_OPcache_volatile_delete -#define arginfo_OPcache_persistent_delete_array arginfo_OPcache_volatile_delete_array +#define arginfo_OPcache_pinned_delete_array arginfo_OPcache_volatile_delete_array -#define arginfo_OPcache_persistent_clear arginfo_OPcache_volatile_clear +#define arginfo_OPcache_pinned_clear arginfo_OPcache_volatile_clear -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_persistent_atomic_increment, 0, 1, IS_LONG, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_atomic_increment, 0, 1, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, step, IS_LONG, 0, "1") ZEND_END_ARG_INFO() -#define arginfo_OPcache_persistent_atomic_decrement arginfo_OPcache_persistent_atomic_increment +#define arginfo_OPcache_pinned_atomic_decrement arginfo_OPcache_pinned_atomic_increment -#define arginfo_OPcache_persistent_cache_info arginfo_OPcache_volatile_cache_info +#define arginfo_OPcache_pinned_cache_info arginfo_OPcache_volatile_cache_info ZEND_BEGIN_ARG_INFO_EX(arginfo_class_OPcache_StaticCacheInfo___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -135,19 +135,19 @@ ZEND_FUNCTION(OPcache_volatile_delete); ZEND_FUNCTION(OPcache_volatile_delete_array); ZEND_FUNCTION(OPcache_volatile_clear); ZEND_FUNCTION(OPcache_volatile_cache_info); -ZEND_FUNCTION(OPcache_persistent_store); -ZEND_FUNCTION(OPcache_persistent_store_array); -ZEND_FUNCTION(OPcache_persistent_fetch); -ZEND_FUNCTION(OPcache_persistent_fetch_array); -ZEND_FUNCTION(OPcache_persistent_exists); -ZEND_FUNCTION(OPcache_persistent_lock); -ZEND_FUNCTION(OPcache_persistent_unlock); -ZEND_FUNCTION(OPcache_persistent_delete); -ZEND_FUNCTION(OPcache_persistent_delete_array); -ZEND_FUNCTION(OPcache_persistent_clear); -ZEND_FUNCTION(OPcache_persistent_atomic_increment); -ZEND_FUNCTION(OPcache_persistent_atomic_decrement); -ZEND_FUNCTION(OPcache_persistent_cache_info); +ZEND_FUNCTION(OPcache_pinned_store); +ZEND_FUNCTION(OPcache_pinned_store_array); +ZEND_FUNCTION(OPcache_pinned_fetch); +ZEND_FUNCTION(OPcache_pinned_fetch_array); +ZEND_FUNCTION(OPcache_pinned_exists); +ZEND_FUNCTION(OPcache_pinned_lock); +ZEND_FUNCTION(OPcache_pinned_unlock); +ZEND_FUNCTION(OPcache_pinned_delete); +ZEND_FUNCTION(OPcache_pinned_delete_array); +ZEND_FUNCTION(OPcache_pinned_clear); +ZEND_FUNCTION(OPcache_pinned_atomic_increment); +ZEND_FUNCTION(OPcache_pinned_atomic_decrement); +ZEND_FUNCTION(OPcache_pinned_cache_info); ZEND_METHOD(OPcache_StaticCacheInfo, __construct); ZEND_METHOD(OPcache_VolatileStatic, __construct); @@ -171,19 +171,19 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_delete_array"), zif_OPcache_volatile_delete_array, arginfo_OPcache_volatile_delete_array, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_clear"), zif_OPcache_volatile_clear, arginfo_OPcache_volatile_clear, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "volatile_cache_info"), zif_OPcache_volatile_cache_info, arginfo_OPcache_volatile_cache_info, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_store"), zif_OPcache_persistent_store, arginfo_OPcache_persistent_store, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_store_array"), zif_OPcache_persistent_store_array, arginfo_OPcache_persistent_store_array, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_fetch"), zif_OPcache_persistent_fetch, arginfo_OPcache_persistent_fetch, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_fetch_array"), zif_OPcache_persistent_fetch_array, arginfo_OPcache_persistent_fetch_array, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_exists"), zif_OPcache_persistent_exists, arginfo_OPcache_persistent_exists, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_lock"), zif_OPcache_persistent_lock, arginfo_OPcache_persistent_lock, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_unlock"), zif_OPcache_persistent_unlock, arginfo_OPcache_persistent_unlock, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_delete"), zif_OPcache_persistent_delete, arginfo_OPcache_persistent_delete, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_delete_array"), zif_OPcache_persistent_delete_array, arginfo_OPcache_persistent_delete_array, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_clear"), zif_OPcache_persistent_clear, arginfo_OPcache_persistent_clear, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_atomic_increment"), zif_OPcache_persistent_atomic_increment, arginfo_OPcache_persistent_atomic_increment, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_atomic_decrement"), zif_OPcache_persistent_atomic_decrement, arginfo_OPcache_persistent_atomic_decrement, 0, NULL, NULL) - ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "persistent_cache_info"), zif_OPcache_persistent_cache_info, arginfo_OPcache_persistent_cache_info, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_store"), zif_OPcache_pinned_store, arginfo_OPcache_pinned_store, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_store_array"), zif_OPcache_pinned_store_array, arginfo_OPcache_pinned_store_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_fetch"), zif_OPcache_pinned_fetch, arginfo_OPcache_pinned_fetch, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_fetch_array"), zif_OPcache_pinned_fetch_array, arginfo_OPcache_pinned_fetch_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_exists"), zif_OPcache_pinned_exists, arginfo_OPcache_pinned_exists, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_lock"), zif_OPcache_pinned_lock, arginfo_OPcache_pinned_lock, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_unlock"), zif_OPcache_pinned_unlock, arginfo_OPcache_pinned_unlock, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_delete"), zif_OPcache_pinned_delete, arginfo_OPcache_pinned_delete, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_delete_array"), zif_OPcache_pinned_delete_array, arginfo_OPcache_pinned_delete_array, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_clear"), zif_OPcache_pinned_clear, arginfo_OPcache_pinned_clear, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_atomic_increment"), zif_OPcache_pinned_atomic_increment, arginfo_OPcache_pinned_atomic_increment, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_atomic_decrement"), zif_OPcache_pinned_atomic_decrement, arginfo_OPcache_pinned_atomic_decrement, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("OPcache", "pinned_cache_info"), zif_OPcache_pinned_cache_info, arginfo_OPcache_pinned_cache_info, 0, NULL, NULL) ZEND_FE_END }; @@ -277,17 +277,17 @@ static zend_class_entry *register_class_OPcache_StaticCacheInfo(void) return class_entry; } -static zend_class_entry *register_class_OPcache_PersistentStatic(void) +static zend_class_entry *register_class_OPcache_PinnedStatic(void) { zend_class_entry ce, *class_entry; - INIT_NS_CLASS_ENTRY(ce, "OPcache", "PersistentStatic", NULL); + INIT_NS_CLASS_ENTRY(ce, "OPcache", "PinnedStatic", NULL); class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); - zend_string *attribute_name_Attribute_class_OPcache_PersistentStatic_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); - zend_attribute *attribute_Attribute_class_OPcache_PersistentStatic_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_OPcache_PersistentStatic_0, 1); - zend_string_release_ex(attribute_name_Attribute_class_OPcache_PersistentStatic_0, true); - ZVAL_LONG(&attribute_Attribute_class_OPcache_PersistentStatic_0->args[0].value, 13); + zend_string *attribute_name_Attribute_class_OPcache_PinnedStatic_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); + zend_attribute *attribute_Attribute_class_OPcache_PinnedStatic_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_OPcache_PinnedStatic_0, 1); + zend_string_release_ex(attribute_name_Attribute_class_OPcache_PinnedStatic_0, true); + ZVAL_LONG(&attribute_Attribute_class_OPcache_PinnedStatic_0->args[0].value, 13); return class_entry; } diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt index 73e0041b742a..5fdf159c83c9 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache PersistentStatic class blob survives dynamic method statics with JIT enabled across requests +FPM: OPcache PinnedStatic class blob survives dynamic method statics with JIT enabled across requests --SKIPIF-- entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; + echo pinned_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\pinned_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; $last = 0; for ($i = 0; $i < 4; $i++) { @@ -83,13 +83,13 @@ if ($request === 2) { return; } -echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)); +echo pinned_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\pinned_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)); PHP; $tester = new FPM\Tester($cfg, $code); $tester->start(iniEntries: [ 'opcache.enable' => '1', - 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.static_cache.pinned_size_mb' => '32', 'opcache.file_update_protection' => '0', 'opcache.jit' => 'tracing', 'opcache.jit_buffer_size' => '64M', @@ -117,5 +117,5 @@ Done diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt index 54e83edb9acc..46f8e2b322e8 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_class_blob_persistence_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache PersistentStatic class blob persists across requests +FPM: OPcache PinnedStatic class blob persists across requests --SKIPIF-- --FILE-- @@ -20,7 +20,7 @@ EOT; $code = <<<'PHP' entry_count, "\n"; +echo CombinedBlobState::$propertyCount, ',', CombinedBlobState::$propertyBag['numbers'][0], ',', CombinedBlobState::nextMethod(), ',', OPcache\pinned_cache_info()->entry_count, "\n"; CombinedBlobState::$propertyCount++; CombinedBlobState::$propertyBag['numbers'][] = 11; @@ -52,7 +52,7 @@ PHP; $tester = new FPM\Tester($cfg, $code); $tester->start(iniEntries: [ 'opcache.enable' => '1', - 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.static_cache.pinned_size_mb' => '32', ]); $tester->expectLogStartNotices(); diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_complex_value_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_complex_value_persistence_001.phpt index 90c821c5e5a3..a22ae6a52228 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_complex_value_persistence_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_complex_value_persistence_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache PersistentStatic persists complex values across requests +FPM: OPcache PinnedStatic persists complex values across requests --SKIPIF-- --FILE-- @@ -25,7 +25,7 @@ class CounterBox public function __construct(public int $value) {} } -#[OPcache\PersistentStatic] +#[OPcache\PinnedStatic] class ComplexState { public static ?CounterBox $box = null; @@ -63,7 +63,7 @@ PHP; $tester = new FPM\Tester($cfg, $code); $tester->start(iniEntries: [ 'opcache.enable' => '1', - 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.static_cache.pinned_size_mb' => '32', ]); $tester->expectLogStartNotices(); diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt index 6013b5fe42c7..91e15d8c4dba 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_object_assignment_snapshot_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache PersistentStatic snapshots object assignments without following object property writes +FPM: OPcache PinnedStatic snapshots object assignments without following object property writes --SKIPIF-- --FILE-- @@ -22,27 +22,27 @@ $code = <<<'PHP' 1]; echo NestedObjectPropertyState::$propertyState->count, "\n"; -echo OPcache\persistent_cache_info()->entry_count, "\n"; +echo OPcache\pinned_cache_info()->entry_count, "\n"; NestedObjectPropertyState::$propertyState->count++; echo NestedObjectPropertyState::$propertyState->count, "\n"; -echo OPcache\persistent_cache_info()->entry_count, "\n"; +echo OPcache\pinned_cache_info()->entry_count, "\n"; NestedObjectPropertyState::$propertyState->count = 10; echo NestedObjectPropertyState::$propertyState->count, "\n"; -echo OPcache\persistent_cache_info()->entry_count; +echo OPcache\pinned_cache_info()->entry_count; PHP; $tester = new FPM\Tester($cfg, $code); $tester->start(iniEntries: [ 'opcache.enable' => '1', - 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.static_cache.pinned_size_mb' => '32', 'opcache.optimization_level' => '0', 'opcache.file_update_protection' => '0', 'opcache.jit' => '0', diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_persistence_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_persistence_001.phpt index 512999522da6..8170fbf45514 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_persistence_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_persistence_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache PersistentStatic persists across requests +FPM: OPcache PinnedStatic persists across requests --SKIPIF-- --FILE-- @@ -20,7 +20,7 @@ EOT; $code = <<<'PHP' start(iniEntries: [ 'opcache.enable' => '1', - 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.static_cache.pinned_size_mb' => '32', ]); $tester->expectLogStartNotices(); diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt index a810694022a1..f60765364db0 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_property_array_publish_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache PersistentStatic publishes nested array writes for property-scoped state +FPM: OPcache PinnedStatic publishes nested array writes for property-scoped state --SKIPIF-- --FILE-- @@ -22,23 +22,23 @@ $code = <<<'PHP' [1]]; echo json_encode(NestedArrayPropertyState::$propertyState['numbers']), "\n"; -echo OPcache\persistent_cache_info()->entry_count, "\n"; +echo OPcache\pinned_cache_info()->entry_count, "\n"; NestedArrayPropertyState::$propertyState['numbers'][] = 2; echo json_encode(NestedArrayPropertyState::$propertyState['numbers']), "\n"; -echo OPcache\persistent_cache_info()->entry_count; +echo OPcache\pinned_cache_info()->entry_count; PHP; $tester = new FPM\Tester($cfg, $code); $tester->start(iniEntries: [ 'opcache.enable' => '1', - 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.static_cache.pinned_size_mb' => '32', 'opcache.optimization_level' => '0', 'opcache.file_update_protection' => '0', 'opcache.jit' => '0', diff --git a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_scope_writes_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_scope_writes_001.phpt index a1138952a85c..5de73a526526 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_scope_writes_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_persistent_static_scope_writes_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache PersistentStatic class, property, and method state handles sequential writes by scope +FPM: OPcache PinnedStatic class, property, and method state handles sequential writes by scope --SKIPIF-- --FILE-- @@ -20,7 +20,7 @@ EOT; $code = <<<'PHP' 0, 'numbers' => []]; @@ -35,13 +35,13 @@ class FpmRaceClassState class FpmRacePropertyState { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static array $data = ['value' => 0, 'numbers' => []]; } class FpmRaceMethodState { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static function next(): int { static $value = 0; @@ -96,7 +96,7 @@ PHP; $tester = new FPM\Tester($cfg, $code); $tester->start(iniEntries: [ 'opcache.enable' => '1', - 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.static_cache.pinned_size_mb' => '32', 'opcache.file_update_protection' => '0', ]); $tester->expectLogStartNotices(); diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_persistent_cache_shared_workers_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_persistent_cache_shared_workers_001.phpt index 8581e32fc4ea..a709a4d5f60a 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_volatile_persistent_cache_shared_workers_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_persistent_cache_shared_workers_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache volatile and persistent caches are shared across static workers +FPM: OPcache volatile and pinned caches are shared across static workers --SKIPIF-- --FILE-- @@ -20,8 +20,8 @@ EOT; $code = <<<'PHP' start(iniEntries: [ 'opcache.enable' => '1', 'opcache.static_cache.volatile_size_mb' => '32', - 'opcache.static_cache.persistent_size_mb' => '32', + 'opcache.static_cache.pinned_size_mb' => '32', ]); $tester->expectLogStartNotices(); $seedPid = parseSeedPid($tester->request(query: 'action=seed')->getBody()); expectCrossWorkerValue($tester, 'fetch_volatile', $seedPid, 'stored-value'); -expectCrossWorkerValue($tester, 'fetch_persistent', $seedPid, '42'); +expectCrossWorkerValue($tester, 'fetch_pinned', $seedPid, '42'); $tester->terminate(); $tester->expectLogTerminatingNotices(); diff --git a/ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c b/ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c index 911669534797..2c45e1f93b46 100644 --- a/ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c +++ b/ext/opcache/tests/helpers/explicit_cache_store_delete_zts_threads_001.c @@ -54,12 +54,12 @@ static const char opcache_test_ini[] = "opcache.memory_consumption=64\n" "opcache.max_accelerated_files=200\n" "opcache.static_cache.volatile_size_mb=8\n" - "opcache.static_cache.persistent_size_mb=8\n\0"; + "opcache.static_cache.pinned_size_mb=8\n\0"; static const char init_code[] = "(static function (): bool {" " OPcache\\volatile_clear();" - " OPcache\\persistent_clear();" + " OPcache\\pinned_clear();" " return true;" "})()"; @@ -68,30 +68,30 @@ static const char seed_code[] = " if (!OPcache\\volatile_store('zts_v_first', str_repeat('A', 1800000))) return false;" " if (!OPcache\\volatile_store('zts_v_second', str_repeat('B', 1800000))) return false;" " if (!OPcache\\volatile_store('zts_v_third', str_repeat('C', 1800000))) return false;" - " OPcache\\persistent_store('zts_p_first', str_repeat('A', 1800000));" - " OPcache\\persistent_store('zts_p_second', str_repeat('B', 1800000));" - " OPcache\\persistent_store('zts_p_third', str_repeat('C', 1800000));" + " OPcache\\pinned_store('zts_p_first', str_repeat('A', 1800000));" + " OPcache\\pinned_store('zts_p_second', str_repeat('B', 1800000));" + " OPcache\\pinned_store('zts_p_third', str_repeat('C', 1800000));" " return true;" "})()"; static const char delete_code[] = "(static function (): bool {" " OPcache\\volatile_delete('zts_v_second');" - " OPcache\\persistent_delete('zts_p_second');" + " OPcache\\pinned_delete('zts_p_second');" " return OPcache\\volatile_fetch('zts_v_second', 'missing') === 'missing'" - " && OPcache\\persistent_fetch('zts_p_second', 'missing') === 'missing';" + " && OPcache\\pinned_fetch('zts_p_second', 'missing') === 'missing';" "})()"; static const char refill_code[] = "(static function (): bool {" " if (!OPcache\\volatile_store('zts_v_replacement', str_repeat('R', 1500000))) return false;" - " OPcache\\persistent_store('zts_p_replacement', str_repeat('R', 1500000));" + " OPcache\\pinned_store('zts_p_replacement', str_repeat('R', 1500000));" " return strlen(OPcache\\volatile_fetch('zts_v_first')) === 1800000" " && strlen(OPcache\\volatile_fetch('zts_v_third')) === 1800000" " && strlen(OPcache\\volatile_fetch('zts_v_replacement')) === 1500000" - " && strlen(OPcache\\persistent_fetch('zts_p_first')) === 1800000" - " && strlen(OPcache\\persistent_fetch('zts_p_third')) === 1800000" - " && strlen(OPcache\\persistent_fetch('zts_p_replacement')) === 1500000;" + " && strlen(OPcache\\pinned_fetch('zts_p_first')) === 1800000" + " && strlen(OPcache\\pinned_fetch('zts_p_third')) === 1800000" + " && strlen(OPcache\\pinned_fetch('zts_p_replacement')) === 1500000;" "})()"; static void zend_opcache_thread_set_failure(zend_opcache_thread_ctx *ctx, const char *message) diff --git a/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c b/ext/opcache/tests/helpers/pinned_static_zts_threads_001.c similarity index 96% rename from ext/opcache/tests/helpers/persistent_static_zts_threads_001.c rename to ext/opcache/tests/helpers/pinned_static_zts_threads_001.c index 8c09757cd348..db5bfd8415dc 100644 --- a/ext/opcache/tests/helpers/persistent_static_zts_threads_001.c +++ b/ext/opcache/tests/helpers/pinned_static_zts_threads_001.c @@ -50,7 +50,7 @@ static const char opcache_test_ini[] = "opcache.enable_cli=1\n" "opcache.memory_consumption=64\n" "opcache.max_accelerated_files=200\n" - "opcache.static_cache.persistent_size_mb=32\n\0"; + "opcache.static_cache.pinned_size_mb=32\n\0"; static void zend_opcache_thread_set_failure(zend_opcache_thread_ctx *ctx, const char *message) { @@ -248,13 +248,13 @@ static bool zend_opcache_run_threaded_scenario(const char *scenario_path, char * zend_opcache_thread_ctx reader_ctx; zend_opcache_thread_ctx writer_ctx; - if (!zend_opcache_run_request_mode(scenario_path, "init", "zts persistent_static init", message, message_size)) { + if (!zend_opcache_run_request_mode(scenario_path, "init", "zts pinned_static init", message, message_size)) { return false; } reader_ctx.mode = "reader"; reader_ctx.scenario_path = scenario_path; - reader_ctx.label = "zts persistent_static reader"; + reader_ctx.label = "zts pinned_static reader"; if (!zend_thread_start(&reader_thread, zend_opcache_thread_main, &reader_ctx)) { snprintf(message, message_size, "reader thread creation failed"); return false; @@ -262,7 +262,7 @@ static bool zend_opcache_run_threaded_scenario(const char *scenario_path, char * writer_ctx.mode = "writer"; writer_ctx.scenario_path = scenario_path; - writer_ctx.label = "zts persistent_static writer"; + writer_ctx.label = "zts pinned_static writer"; if (!zend_thread_start(&writer_thread, zend_opcache_thread_main, &writer_ctx)) { snprintf(message, message_size, "writer thread creation failed"); return false; @@ -285,7 +285,7 @@ static bool zend_opcache_run_threaded_scenario(const char *scenario_path, char * return false; } - return zend_opcache_run_request_mode(scenario_path, "verify", "zts persistent_static verify", message, message_size); + return zend_opcache_run_request_mode(scenario_path, "verify", "zts pinned_static verify", message, message_size); } int main(int argc, char **argv) diff --git a/ext/opcache/tests/helpers/persistent_static_zts_threads_001.inc b/ext/opcache/tests/helpers/pinned_static_zts_threads_001.inc similarity index 81% rename from ext/opcache/tests/helpers/persistent_static_zts_threads_001.inc rename to ext/opcache/tests/helpers/pinned_static_zts_threads_001.inc index ce2c0b52beff..57e34bfebc9a 100644 --- a/ext/opcache/tests/helpers/persistent_static_zts_threads_001.inc +++ b/ext/opcache/tests/helpers/pinned_static_zts_threads_001.inc @@ -1,5 +1,5 @@ 0, 'numbers' => []]; @@ -14,13 +14,13 @@ class ZtsRaceClassState class ZtsRacePropertyState { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static array $data = ['value' => 0, 'numbers' => []]; } class ZtsRaceMethodState { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static function next(): int { static $value = 0; @@ -29,7 +29,7 @@ class ZtsRaceMethodState } } -function zts_persistent_static_wait_for_file(string $path): bool +function zts_pinned_static_wait_for_file(string $path): bool { $deadline = microtime(true) + 5.0; while (!file_exists($path)) { @@ -41,7 +41,7 @@ function zts_persistent_static_wait_for_file(string $path): bool return true; } -function zts_persistent_static_tuple(): string +function zts_pinned_static_tuple(): string { return ZtsRaceClassState::$data['value'] . ',' . array_sum(ZtsRaceClassState::$data['numbers']) . ',' @@ -66,7 +66,7 @@ if ($mode === 'init') { } if ($mode === 'writer') { - if (!zts_persistent_static_wait_for_file($readyFile)) { + if (!zts_pinned_static_wait_for_file($readyFile)) { return false; } $classMethod = ZtsRaceClassState::next(); @@ -85,16 +85,16 @@ if ($mode === 'writer') { if ($mode === 'reader') { file_put_contents($readyFile, 'ready'); - if (!zts_persistent_static_wait_for_file($doneFile)) { + if (!zts_pinned_static_wait_for_file($doneFile)) { return false; } - return zts_persistent_static_tuple() === '10,21,3,1,1,3'; + return zts_pinned_static_tuple() === '10,21,3,1,1,3'; } if ($mode === 'verify') { @unlink($readyFile); @unlink($doneFile); - return zts_persistent_static_tuple() === '10,21,4,1,1,4'; + return zts_pinned_static_tuple() === '10,21,4,1,1,4'; } return false; diff --git a/ext/opcache/tests/static_cache_attribute_class_delete_001.phpt b/ext/opcache/tests/static_cache_attribute_class_delete_001.phpt index 093eb21ad24c..6e8f368dbee8 100644 --- a/ext/opcache/tests/static_cache_attribute_class_delete_001.phpt +++ b/ext/opcache/tests/static_cache_attribute_class_delete_001.phpt @@ -9,8 +9,8 @@ server file_put_contents(__DIR__ . '/static_cache_attribute_class_delete_001.php', <<<'PHP' OPcache\persistent_cache_info(), + 'pinned' => OPcache\pinned_cache_info(), 'volatile' => OPcache\volatile_cache_info(), default => throw new RuntimeException('unknown backend'), }; @@ -99,7 +99,7 @@ function class_delete_cache_info(string $backend): OPcache\StaticCacheInfo function class_delete_store(string $backend, string $key, mixed $value): void { match ($backend) { - 'persistent' => OPcache\persistent_store($key, $value), + 'pinned' => OPcache\pinned_store($key, $value), 'volatile' => OPcache\volatile_store($key, $value), default => throw new RuntimeException('unknown backend'), }; @@ -108,7 +108,7 @@ function class_delete_store(string $backend, string $key, mixed $value): void function class_delete_fetch(string $backend, string $key, mixed $default): mixed { return match ($backend) { - 'persistent' => OPcache\persistent_fetch($key, $default), + 'pinned' => OPcache\pinned_fetch($key, $default), 'volatile' => OPcache\volatile_fetch($key, $default), default => throw new RuntimeException('unknown backend'), }; @@ -117,7 +117,7 @@ function class_delete_fetch(string $backend, string $key, mixed $default): mixed function class_delete_delete(string $backend, string $key_or_class): void { match ($backend) { - 'persistent' => OPcache\persistent_delete($key_or_class), + 'pinned' => OPcache\pinned_delete($key_or_class), 'volatile' => OPcache\volatile_delete($key_or_class), default => throw new RuntimeException('unknown backend'), }; @@ -126,7 +126,7 @@ function class_delete_delete(string $backend, string $key_or_class): void function class_delete_blob_class(string $backend): string { return match ($backend) { - 'persistent' => ClassDeletePersistentBlob::class, + 'pinned' => ClassDeletePinnedBlob::class, 'volatile' => ClassDeleteVolatileBlob::class, default => throw new RuntimeException('unknown backend'), }; @@ -135,7 +135,7 @@ function class_delete_blob_class(string $backend): string function class_delete_members_class(string $backend): string { return match ($backend) { - 'persistent' => ClassDeletePersistentMembers::class, + 'pinned' => ClassDeletePinnedMembers::class, 'volatile' => ClassDeleteVolatileMembers::class, default => throw new RuntimeException('unknown backend'), }; @@ -144,7 +144,7 @@ function class_delete_members_class(string $backend): string function class_delete_plain_class(string $backend): string { return match ($backend) { - 'persistent' => ClassDeletePersistentPlain::class, + 'pinned' => ClassDeletePinnedPlain::class, 'volatile' => ClassDeleteVolatilePlain::class, default => throw new RuntimeException('unknown backend'), }; @@ -153,9 +153,9 @@ function class_delete_plain_class(string $backend): string function class_delete_blob_state(string $backend): array { return match ($backend) { - 'persistent' => [ - ClassDeletePersistentBlob::$value, - ClassDeletePersistentBlob::method(), + 'pinned' => [ + ClassDeletePinnedBlob::$value, + ClassDeletePinnedBlob::method(), ], 'volatile' => [ ClassDeleteVolatileBlob::$value, @@ -168,9 +168,9 @@ function class_delete_blob_state(string $backend): array function class_delete_members_state(string $backend): array { return match ($backend) { - 'persistent' => [ - ClassDeletePersistentMembers::$property, - ClassDeletePersistentMembers::method(), + 'pinned' => [ + ClassDeletePinnedMembers::$property, + ClassDeletePinnedMembers::method(), ], 'volatile' => [ ClassDeleteVolatileMembers::$property, @@ -183,9 +183,9 @@ function class_delete_members_state(string $backend): array function class_delete_seed_blob(string $backend): void { switch ($backend) { - case 'persistent': - ClassDeletePersistentBlob::$value = 1; - ClassDeletePersistentBlob::method(1); + case 'pinned': + ClassDeletePinnedBlob::$value = 1; + ClassDeletePinnedBlob::method(1); break; case 'volatile': @@ -201,9 +201,9 @@ function class_delete_seed_blob(string $backend): void function class_delete_seed_members(string $backend): void { switch ($backend) { - case 'persistent': - ClassDeletePersistentMembers::$property = 1; - ClassDeletePersistentMembers::method(1); + case 'pinned': + ClassDeletePinnedMembers::$property = 1; + ClassDeletePinnedMembers::method(1); break; case 'volatile': @@ -233,11 +233,11 @@ function class_delete_dump_members(string $label, string $backend): void } $action = $_GET['action'] ?? 'read_blob'; -$backend = $_GET['backend'] ?? 'persistent'; +$backend = $_GET['backend'] ?? 'pinned'; if ($action === 'reset') { OPcache\volatile_clear(); - OPcache\persistent_clear(); + OPcache\pinned_clear(); opcache_reset(); echo "reset\n"; return; @@ -311,10 +311,10 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); $base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/static_cache_attribute_class_delete_001.php'; -foreach (['persistent', 'volatile'] as $backend) { +foreach (['pinned', 'volatile'] as $backend) { echo file_get_contents($base . '?action=reset'); echo file_get_contents($base . '?action=seed_blob&backend=' . $backend); echo file_get_contents($base . '?action=read_blob&backend=' . $backend); @@ -338,19 +338,19 @@ foreach (['persistent', 'volatile'] as $backend) { ?> --EXPECT-- reset -seed-blob-persistent=1,1,count=1 -read-blob-persistent=1,1,count=1 -delete-blob-persistent=count=0 -read-blob-persistent=0,0,count=0 +seed-blob-pinned=1,1,count=1 +read-blob-pinned=1,1,count=1 +delete-blob-pinned=count=0 +read-blob-pinned=0,0,count=0 reset -seed-members-persistent=1,1,count=2 -read-members-persistent=1,1,count=2 -delete-members-persistent=count=0 -read-members-persistent=0,0,count=0 +seed-members-pinned=1,1,count=2 +read-members-pinned=1,1,count=2 +delete-members-pinned=count=0 +read-members-pinned=0,0,count=0 reset -delete-plain-persistent=count=0 +delete-plain-pinned=count=0 reset -noautoload-persistent=missing,count=0 +noautoload-pinned=missing,count=0 reset seed-blob-volatile=1,1,count=1 read-blob-volatile=1,1,count=1 diff --git a/ext/opcache/tests/static_cache_attribute_exact_key_delete_001.phpt b/ext/opcache/tests/static_cache_attribute_exact_key_delete_001.phpt index b65403b0c5d5..6785441c16a1 100644 --- a/ext/opcache/tests/static_cache_attribute_exact_key_delete_001.phpt +++ b/ext/opcache/tests/static_cache_attribute_exact_key_delete_001.phpt @@ -9,8 +9,8 @@ server file_put_contents(__DIR__ . '/static_cache_attribute_exact_key_delete_001.php', <<<'PHP' OPcache\persistent_cache_info(), + 'pinned' => OPcache\pinned_cache_info(), 'volatile' => OPcache\volatile_cache_info(), default => throw new RuntimeException('unknown backend'), }; @@ -97,11 +97,11 @@ function exact_key_cache_info(string $backend): OPcache\StaticCacheInfo function exact_key_state(string $backend): array { return match ($backend) { - 'persistent' => [ - ExactKeyPersistentClassState::$value, - ExactKeyPersistentClassState::method(), - ExactKeyPersistentPropertyState::$value, - ExactKeyPersistentMethodState::method(), + 'pinned' => [ + ExactKeyPinnedClassState::$value, + ExactKeyPinnedClassState::method(), + ExactKeyPinnedPropertyState::$value, + ExactKeyPinnedMethodState::method(), ], 'volatile' => [ ExactKeyVolatileClassState::$value, @@ -116,11 +116,11 @@ function exact_key_state(string $backend): array function exact_key_seed(string $backend): void { switch ($backend) { - case 'persistent': - ExactKeyPersistentClassState::$value = 1; - ExactKeyPersistentClassState::method(1); - ExactKeyPersistentPropertyState::$value = 1; - ExactKeyPersistentMethodState::method(1); + case 'pinned': + ExactKeyPinnedClassState::$value = 1; + ExactKeyPinnedClassState::method(1); + ExactKeyPinnedPropertyState::$value = 1; + ExactKeyPinnedMethodState::method(1); return; case 'volatile': @@ -138,10 +138,10 @@ function exact_key_seed(string $backend): void function exact_key_delete_individual(string $backend): void { switch ($backend) { - case 'persistent': - OPcache\persistent_delete_array([ - 'persistent_static:ExactKeyPersistentPropertyState::$value', - 'persistent_static:ExactKeyPersistentMethodState::method()::$value', + case 'pinned': + OPcache\pinned_delete_array([ + 'pinned_static:ExactKeyPinnedPropertyState::$value', + 'pinned_static:ExactKeyPinnedMethodState::method()::$value', ]); return; @@ -166,11 +166,11 @@ function exact_key_dump(string $label, string $backend): void } $action = $_GET['action'] ?? 'read'; -$backend = $_GET['backend'] ?? 'persistent'; +$backend = $_GET['backend'] ?? 'pinned'; if ($action === 'reset') { OPcache\volatile_clear(); - OPcache\persistent_clear(); + OPcache\pinned_clear(); opcache_reset(); echo "reset\n"; return; @@ -198,10 +198,10 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); $base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/static_cache_attribute_exact_key_delete_001.php'; -foreach (['persistent', 'volatile'] as $backend) { +foreach (['pinned', 'volatile'] as $backend) { echo file_get_contents($base . '?action=reset'); echo file_get_contents($base . '?action=seed&backend=' . $backend); echo file_get_contents($base . '?action=read&backend=' . $backend); @@ -216,10 +216,10 @@ foreach (['persistent', 'volatile'] as $backend) { ?> --EXPECT-- reset -seed-persistent=1,1,1,1,count=3 -read-persistent=1,1,1,1,count=3 -delete-individual-persistent=count=1 -read-persistent=1,1,0,0,count=1 +seed-pinned=1,1,1,1,count=3 +read-pinned=1,1,1,1,count=3 +delete-individual-pinned=count=1 +read-pinned=1,1,0,0,count=1 reset seed-volatile=1,1,1,1,count=3 read-volatile=1,1,1,1,count=3 diff --git a/ext/opcache/tests/static_cache_attribute_publish_userland_outside_lock_001.phpt b/ext/opcache/tests/static_cache_attribute_publish_userland_outside_lock_001.phpt index 5871e3f946dd..5013fcd3b0a5 100644 --- a/ext/opcache/tests/static_cache_attribute_publish_userland_outside_lock_001.phpt +++ b/ext/opcache/tests/static_cache_attribute_publish_userland_outside_lock_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- backend === 'volatile') { OPcache\volatile_store('publish_inner_volatile', 'ok'); } else { - OPcache\persistent_store('publish_inner_persistent', 'ok'); + OPcache\pinned_store('publish_inner_pinned', 'ok'); } return ['backend' => $this->backend]; @@ -41,9 +41,9 @@ class VolatilePublishTarget public static mixed $value; } -class PersistentPublishTarget +class PinnedPublishTarget { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static mixed $value; } @@ -51,13 +51,13 @@ OPcache\volatile_clear(); VolatilePublishTarget::$value = new ReentrantPublishPayload('volatile'); var_dump(OPcache\volatile_fetch('publish_inner_volatile')); -OPcache\persistent_clear(); -PersistentPublishTarget::$value = new ReentrantPublishPayload('persistent'); -var_dump(OPcache\persistent_fetch('publish_inner_persistent')); +OPcache\pinned_clear(); +PinnedPublishTarget::$value = new ReentrantPublishPayload('pinned'); +var_dump(OPcache\pinned_fetch('publish_inner_pinned')); ?> --EXPECT-- serialize-volatile string(2) "ok" -serialize-persistent +serialize-pinned string(2) "ok" diff --git a/ext/opcache/tests/static_cache_attribute_signatures_001.phpt b/ext/opcache/tests/static_cache_attribute_signatures_001.phpt index 7b3d7bff34b0..c03781f517c4 100644 --- a/ext/opcache/tests/static_cache_attribute_signatures_001.phpt +++ b/ext/opcache/tests/static_cache_attribute_signatures_001.phpt @@ -38,7 +38,7 @@ try { echo "readonly-ttl\n"; } -var_dump((new ReflectionClass(OPcache\PersistentStatic::class))->getConstructor()); +var_dump((new ReflectionClass(OPcache\PinnedStatic::class))->getConstructor()); ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt b/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt index 665089635d89..f9ea05325002 100644 --- a/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt +++ b/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt @@ -7,7 +7,7 @@ spl opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=0 -opcache.static_cache.persistent_size_mb=0 +opcache.static_cache.pinned_size_mb=0 --FILE-- format('Y-m-d H:i:s')); -OPcache\persistent_store('persistent_user', new ExplicitPreparedUser('Carol', 40)); -$persistent = OPcache\persistent_fetch('persistent_user'); +OPcache\pinned_store('pinned_user', new ExplicitPreparedUser('Carol', 40)); +$pinned = OPcache\pinned_fetch('pinned_user'); -var_dump($persistent instanceof ExplicitPreparedUser); -var_dump($persistent->name); -var_dump($persistent->age); +var_dump($pinned instanceof ExplicitPreparedUser); +var_dump($pinned->name); +var_dump($pinned->age); var_dump(OPcache\volatile_store_array([])); diff --git a/ext/opcache/tests/static_cache_explicit_cache_destructive_mutators_no_deadlock_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_destructive_mutators_no_deadlock_001.phpt index 1a4dfc3f16b5..c300fa19ea7e 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_destructive_mutators_no_deadlock_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_destructive_mutators_no_deadlock_001.phpt @@ -13,7 +13,7 @@ if (!function_exists('pcntl_fork')) { opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- cache_clear('volatile')); -run_reservation_cycle('persistent', 'clear', static fn () => cache_clear('persistent')); +run_reservation_cycle('pinned', 'clear', static fn () => cache_clear('pinned')); run_reservation_cycle('volatile', 'reset', static fn () => opcache_reset()); run_delete_wait_cycle('volatile'); -run_delete_wait_cycle('persistent'); +run_delete_wait_cycle('pinned'); ?> --EXPECT-- volatile clear: no deadlock -persistent clear: no deadlock +pinned clear: no deadlock volatile reset: no deadlock volatile delete: no wait volatile delete fetch: MISS -persistent delete: no wait -persistent delete fetch: MISS +pinned delete: no wait +pinned delete fetch: MISS --CLEAN-- string(5) "value" diff --git a/ext/opcache/tests/static_cache_explicit_cache_fetch_userland_outside_lock_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_fetch_userland_outside_lock_001.phpt index bfaee7454dc2..8e162be32c68 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_fetch_userland_outside_lock_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_fetch_userland_outside_lock_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- backend === 'volatile') { OPcache\volatile_store('fetch_inner_volatile', 'ok'); } else { - OPcache\persistent_store('fetch_inner_persistent', 'ok'); + OPcache\pinned_store('fetch_inner_pinned', 'ok'); } } } -foreach (['volatile', 'persistent'] as $backend) { +foreach (['volatile', 'pinned'] as $backend) { echo $backend, "\n"; if ($backend === 'volatile') { @@ -43,10 +43,10 @@ foreach (['volatile', 'persistent'] as $backend) { var_dump(OPcache\volatile_fetch('fetch_payload') instanceof ReentrantFetchPayload); var_dump(OPcache\volatile_fetch('fetch_inner_volatile')); } else { - OPcache\persistent_clear(); - OPcache\persistent_store('fetch_payload', new ReentrantFetchPayload($backend)); - var_dump(OPcache\persistent_fetch('fetch_payload') instanceof ReentrantFetchPayload); - var_dump(OPcache\persistent_fetch('fetch_inner_persistent')); + OPcache\pinned_clear(); + OPcache\pinned_store('fetch_payload', new ReentrantFetchPayload($backend)); + var_dump(OPcache\pinned_fetch('fetch_payload') instanceof ReentrantFetchPayload); + var_dump(OPcache\pinned_fetch('fetch_inner_pinned')); } } @@ -56,7 +56,7 @@ volatile unserialize-volatile bool(true) string(2) "ok" -persistent -unserialize-persistent +pinned +unserialize-pinned bool(true) string(2) "ok" diff --git a/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_001.phpt index 36b3dc103ec2..981651f6d6e8 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_001.phpt @@ -1,12 +1,12 @@ --TEST-- -OPcache explicit volatile and persistent caches relocate fragmented payload blocks before store failure +OPcache explicit volatile and pinned caches relocate fragmented payload blocks before store failure --EXTENSIONS-- opcache --INI-- opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=8 -opcache.static_cache.persistent_size_mb=8 +opcache.static_cache.pinned_size_mb=8 --FILE-- --EXPECT-- @@ -81,7 +81,7 @@ int(2400000) int(1200000) int(1200000) int(1200000) --- persistent -- +-- pinned -- bool(true) bool(true) bool(true) diff --git a/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt index 75d316599f01..fd920a16bb07 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=8 -opcache.static_cache.persistent_size_mb=8 +opcache.static_cache.pinned_size_mb=8 --FILE-- --EXPECT-- @@ -94,7 +94,7 @@ bool(false) int(1200000) int(1200000) int(1200000) --- persistent -- +-- pinned -- bool(true) bool(true) bool(true) diff --git a/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt index 64071e844222..edfee5ec024d 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_key_validation_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- $store('', 'value')); dump_error('store-array-empty', static fn () => $storeArray(['' => 'value'])); @@ -79,11 +79,11 @@ foreach (['volatile', 'persistent'] as $backend) { dump_error('delete-array-reserved-class', static fn () => $deleteArray([$reservedClassKey])); dump_error('delete-array-loaded-class', static fn () => $deleteArray([$loadedClassKey])); - if ($backend === 'persistent') { - dump_error('atomic-increment-reserved-class', static fn () => OPcache\persistent_atomic_increment($reservedClassKey)); - dump_error('atomic-decrement-reserved-class', static fn () => OPcache\persistent_atomic_decrement($reservedClassKey)); - dump_error('atomic-increment-loaded-class', static fn () => OPcache\persistent_atomic_increment($loadedClassKey)); - dump_error('atomic-decrement-loaded-class', static fn () => OPcache\persistent_atomic_decrement($loadedClassKey)); + if ($backend === 'pinned') { + dump_error('atomic-increment-reserved-class', static fn () => OPcache\pinned_atomic_increment($reservedClassKey)); + dump_error('atomic-decrement-reserved-class', static fn () => OPcache\pinned_atomic_decrement($reservedClassKey)); + dump_error('atomic-increment-loaded-class', static fn () => OPcache\pinned_atomic_increment($loadedClassKey)); + dump_error('atomic-decrement-loaded-class', static fn () => OPcache\pinned_atomic_decrement($loadedClassKey)); } } @@ -119,37 +119,37 @@ delete-array-empty: ValueError: OPcache\volatile_delete_array(): Argument #1 ($k delete-array-object: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names delete-array-reserved-class: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names delete-array-loaded-class: ValueError: OPcache\volatile_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -persistent -store-empty: ValueError: OPcache\persistent_store(): Argument #1 ($key) must be a non-empty string -store-array-empty: ValueError: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names -store-reserved-class: ValueError: OPcache\persistent_store(): Argument #1 ($key) must not start with a reserved static-cache class key prefix -store-array-reserved-class: ValueError: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names -store-loaded-class: ValueError: OPcache\persistent_store(): Argument #1 ($key) must not be a loaded class name -store-array-loaded-class: ValueError: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names -fetch-empty: ValueError: OPcache\persistent_fetch(): Argument #1 ($key) must be a non-empty string -fetch-array-empty: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -fetch-array-object: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -fetch-reserved-class: ValueError: OPcache\persistent_fetch(): Argument #1 ($key) must not start with a reserved static-cache class key prefix -fetch-array-reserved-class: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -fetch-loaded-class: ValueError: OPcache\persistent_fetch(): Argument #1 ($key) must not be a loaded class name -fetch-array-loaded-class: ValueError: OPcache\persistent_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -exists-empty: ValueError: OPcache\persistent_exists(): Argument #1 ($key) must be a non-empty string -exists-reserved-class: ValueError: OPcache\persistent_exists(): Argument #1 ($key) must not start with a reserved static-cache class key prefix -exists-loaded-class: ValueError: OPcache\persistent_exists(): Argument #1 ($key) must not be a loaded class name -lock-empty: ValueError: OPcache\persistent_lock(): Argument #1 ($key) must be a non-empty string -lock-negative-lease: ValueError: OPcache\persistent_lock(): Argument #2 ($lease) must be greater than or equal to 0 -lock-reserved-class: ValueError: OPcache\persistent_lock(): Argument #1 ($key) must not start with a reserved static-cache class key prefix -lock-loaded-class: ValueError: OPcache\persistent_lock(): Argument #1 ($key) must not be a loaded class name -unlock-empty: ValueError: OPcache\persistent_unlock(): Argument #1 ($key) must be a non-empty string -unlock-reserved-class: ValueError: OPcache\persistent_unlock(): Argument #1 ($key) must not start with a reserved static-cache class key prefix -unlock-loaded-class: ValueError: OPcache\persistent_unlock(): Argument #1 ($key) must not be a loaded class name -delete-empty: ValueError: OPcache\persistent_delete(): Argument #1 ($key_or_class) must be a non-empty string -delete-reserved-class: ValueError: OPcache\persistent_delete(): Argument #1 ($key_or_class) must not start with a reserved static-cache class key prefix -delete-array-empty: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -delete-array-object: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -delete-array-reserved-class: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -delete-array-loaded-class: ValueError: OPcache\persistent_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names -atomic-increment-reserved-class: ValueError: OPcache\persistent_atomic_increment(): Argument #1 ($key) must not start with a reserved static-cache class key prefix -atomic-decrement-reserved-class: ValueError: OPcache\persistent_atomic_decrement(): Argument #1 ($key) must not start with a reserved static-cache class key prefix -atomic-increment-loaded-class: ValueError: OPcache\persistent_atomic_increment(): Argument #1 ($key) must not be a loaded class name -atomic-decrement-loaded-class: ValueError: OPcache\persistent_atomic_decrement(): Argument #1 ($key) must not be a loaded class name +pinned +store-empty: ValueError: OPcache\pinned_store(): Argument #1 ($key) must be a non-empty string +store-array-empty: ValueError: OPcache\pinned_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names +store-reserved-class: ValueError: OPcache\pinned_store(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +store-array-reserved-class: ValueError: OPcache\pinned_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names +store-loaded-class: ValueError: OPcache\pinned_store(): Argument #1 ($key) must not be a loaded class name +store-array-loaded-class: ValueError: OPcache\pinned_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names +fetch-empty: ValueError: OPcache\pinned_fetch(): Argument #1 ($key) must be a non-empty string +fetch-array-empty: ValueError: OPcache\pinned_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-array-object: ValueError: OPcache\pinned_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-reserved-class: ValueError: OPcache\pinned_fetch(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +fetch-array-reserved-class: ValueError: OPcache\pinned_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +fetch-loaded-class: ValueError: OPcache\pinned_fetch(): Argument #1 ($key) must not be a loaded class name +fetch-array-loaded-class: ValueError: OPcache\pinned_fetch_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +exists-empty: ValueError: OPcache\pinned_exists(): Argument #1 ($key) must be a non-empty string +exists-reserved-class: ValueError: OPcache\pinned_exists(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +exists-loaded-class: ValueError: OPcache\pinned_exists(): Argument #1 ($key) must not be a loaded class name +lock-empty: ValueError: OPcache\pinned_lock(): Argument #1 ($key) must be a non-empty string +lock-negative-lease: ValueError: OPcache\pinned_lock(): Argument #2 ($lease) must be greater than or equal to 0 +lock-reserved-class: ValueError: OPcache\pinned_lock(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +lock-loaded-class: ValueError: OPcache\pinned_lock(): Argument #1 ($key) must not be a loaded class name +unlock-empty: ValueError: OPcache\pinned_unlock(): Argument #1 ($key) must be a non-empty string +unlock-reserved-class: ValueError: OPcache\pinned_unlock(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +unlock-loaded-class: ValueError: OPcache\pinned_unlock(): Argument #1 ($key) must not be a loaded class name +delete-empty: ValueError: OPcache\pinned_delete(): Argument #1 ($key_or_class) must be a non-empty string +delete-reserved-class: ValueError: OPcache\pinned_delete(): Argument #1 ($key_or_class) must not start with a reserved static-cache class key prefix +delete-array-empty: ValueError: OPcache\pinned_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-object: ValueError: OPcache\pinned_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-reserved-class: ValueError: OPcache\pinned_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +delete-array-loaded-class: ValueError: OPcache\pinned_delete_array(): Argument #1 ($keys) must contain only non-empty string or int cache keys that are not reserved static-cache class keys or loaded class names +atomic-increment-reserved-class: ValueError: OPcache\pinned_atomic_increment(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +atomic-decrement-reserved-class: ValueError: OPcache\pinned_atomic_decrement(): Argument #1 ($key) must not start with a reserved static-cache class key prefix +atomic-increment-loaded-class: ValueError: OPcache\pinned_atomic_increment(): Argument #1 ($key) must not be a loaded class name +atomic-decrement-loaded-class: ValueError: OPcache\pinned_atomic_decrement(): Argument #1 ($key) must not be a loaded class name diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_clear_reset_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_clear_reset_001.phpt index 307425c6ffc3..d6300c604082 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_lock_clear_reset_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_clear_reset_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- --EXPECT-- @@ -62,7 +62,7 @@ volatile bool(true) bool(true) bool(true) -persistent +pinned bool(true) bool(true) bool(true) diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_fork_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_fork_001.phpt index dd9f5121d578..7bba1534cc05 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_lock_fork_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_fork_001.phpt @@ -13,7 +13,7 @@ if (!function_exists('pcntl_fork')) { opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- print 'delete_array value: ' . cache_fetch($backend, $key, 'MISS') . "\n", ); - if ($backend === 'persistent') { + if ($backend === 'pinned') { $key = $baseKey . '_atomic'; reserve_missing($backend, $key); run_blocked_mutator( $backend, 'atomic', - static fn () => OPcache\persistent_atomic_increment($key, 2), + static fn () => OPcache\pinned_atomic_increment($key, 2), static fn () => cache_store($backend, $key, 10), static fn () => print 'atomic value: ' . cache_fetch($backend, $key, 'MISS') . "\n", ); @@ -205,7 +205,7 @@ function run_backend_mutators(string $backend): void run_blocked_mutator( $backend, 'atomic_decrement', - static fn () => OPcache\persistent_atomic_decrement($key, 3), + static fn () => OPcache\pinned_atomic_decrement($key, 3), static fn () => cache_store($backend, $key, 10), static fn () => print 'atomic_decrement value: ' . cache_fetch($backend, $key, 'MISS') . "\n", ); @@ -227,7 +227,7 @@ function run_backend_mutators(string $backend): void } run_backend_mutators('volatile'); -run_backend_mutators('persistent'); +run_backend_mutators('pinned'); ?> --EXPECT-- @@ -253,7 +253,7 @@ bool(true) clear blocked: no clear result: null clear values: owner,MISS -persistent +pinned bool(true) store blocked: yes store result: null diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_rshutdown_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_rshutdown_001.phpt index b796b82593ea..b6a788bb3045 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_lock_rshutdown_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_rshutdown_001.phpt @@ -13,7 +13,7 @@ if (!function_exists('pcntl_fork')) { opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- --EXPECT-- @@ -118,7 +118,7 @@ volatile blocked released bool(true) -persistent +pinned blocked released --CLEAN-- diff --git a/ext/opcache/tests/static_cache_explicit_cache_prepare_nested_object_reuse_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_prepare_nested_object_reuse_001.phpt index 0201c531a4e0..d414b9382294 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_prepare_nested_object_reuse_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_prepare_nested_object_reuse_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- label = 'leaf-second'; @@ -33,12 +33,12 @@ $payload['leaf']->revision = 2; $payload['rows'][0]['state'] = 'beta'; var_dump(OPcache\volatile_store('nested_volatile_second', $payload)); -OPcache\persistent_store('nested_persistent_second', $payload); +OPcache\pinned_store('nested_pinned_second', $payload); $volatileFirst = OPcache\volatile_fetch('nested_volatile_first'); $volatileSecond = OPcache\volatile_fetch('nested_volatile_second'); -$persistentFirst = OPcache\persistent_fetch('nested_persistent_first'); -$persistentSecond = OPcache\persistent_fetch('nested_persistent_second'); +$pinnedFirst = OPcache\pinned_fetch('nested_pinned_first'); +$pinnedSecond = OPcache\pinned_fetch('nested_pinned_second'); echo $volatileFirst['name'], "\n"; echo $volatileFirst['leaf']->label, "\n"; @@ -48,14 +48,14 @@ echo $volatileSecond['name'], "\n"; echo $volatileSecond['leaf']->label, "\n"; echo $volatileSecond['leaf']->revision, "\n"; echo $volatileSecond['rows'][0]['state'], "\n"; -echo $persistentFirst['name'], "\n"; -echo $persistentFirst['leaf']->label, "\n"; -echo $persistentFirst['leaf']->revision, "\n"; -echo $persistentFirst['rows'][0]['state'], "\n"; -echo $persistentSecond['name'], "\n"; -echo $persistentSecond['leaf']->label, "\n"; -echo $persistentSecond['leaf']->revision, "\n"; -echo $persistentSecond['rows'][0]['state'], "\n"; +echo $pinnedFirst['name'], "\n"; +echo $pinnedFirst['leaf']->label, "\n"; +echo $pinnedFirst['leaf']->revision, "\n"; +echo $pinnedFirst['rows'][0]['state'], "\n"; +echo $pinnedSecond['name'], "\n"; +echo $pinnedSecond['leaf']->label, "\n"; +echo $pinnedSecond['leaf']->revision, "\n"; +echo $pinnedSecond['rows'][0]['state'], "\n"; ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_explicit_cache_prepare_safe_direct_mutation_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_prepare_safe_direct_mutation_001.phpt index f9c909abee1e..0af3f3345fb2 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_prepare_safe_direct_mutation_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_prepare_safe_direct_mutation_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- modify('+2 days'); var_dump(OPcache\volatile_store('safe_direct_volatile_second', $payload)); -OPcache\persistent_store('safe_direct_persistent_second', $payload); +OPcache\pinned_store('safe_direct_pinned_second', $payload); $volatileFirst = OPcache\volatile_fetch('safe_direct_volatile_first'); $volatileSecond = OPcache\volatile_fetch('safe_direct_volatile_second'); -$persistentFirst = OPcache\persistent_fetch('safe_direct_persistent_first'); -$persistentSecond = OPcache\persistent_fetch('safe_direct_persistent_second'); +$pinnedFirst = OPcache\pinned_fetch('safe_direct_pinned_first'); +$pinnedSecond = OPcache\pinned_fetch('safe_direct_pinned_second'); echo $volatileFirst['name'], "\n"; echo $volatileFirst['date']->format('Y-m-d H:i:s'), "\n"; echo $volatileSecond['name'], "\n"; echo $volatileSecond['date']->format('Y-m-d H:i:s'), "\n"; -echo $persistentFirst['name'], "\n"; -echo $persistentFirst['date']->format('Y-m-d H:i:s'), "\n"; -echo $persistentSecond['name'], "\n"; -echo $persistentSecond['date']->format('Y-m-d H:i:s'), "\n"; +echo $pinnedFirst['name'], "\n"; +echo $pinnedFirst['date']->format('Y-m-d H:i:s'), "\n"; +echo $pinnedSecond['name'], "\n"; +echo $pinnedSecond['date']->format('Y-m-d H:i:s'), "\n"; ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_explicit_cache_prepare_scratch_reuse_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_prepare_scratch_reuse_001.phpt index e3c8685497d5..5ec336c987d2 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_prepare_scratch_reuse_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_prepare_scratch_reuse_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- name = 'second'; $payload->rows[12]['label'] = str_repeat('B', 24); $payload->rows[12]['nested']['value'] = 777; var_dump(OPcache\volatile_store('scratch_volatile_second', $payload)); -OPcache\persistent_store('scratch_persistent_second', $payload); +OPcache\pinned_store('scratch_pinned_second', $payload); $volatileFirst = OPcache\volatile_fetch('scratch_volatile_first'); $volatileSecond = OPcache\volatile_fetch('scratch_volatile_second'); -$persistentFirst = OPcache\persistent_fetch('scratch_persistent_first'); -$persistentSecond = OPcache\persistent_fetch('scratch_persistent_second'); +$pinnedFirst = OPcache\pinned_fetch('scratch_pinned_first'); +$pinnedSecond = OPcache\pinned_fetch('scratch_pinned_second'); echo $volatileFirst->name, "\n"; echo $volatileFirst->rows[12]['label'], "\n"; @@ -59,12 +59,12 @@ echo $volatileFirst->rows[12]['nested']['value'], "\n"; echo $volatileSecond->name, "\n"; echo $volatileSecond->rows[12]['label'], "\n"; echo $volatileSecond->rows[12]['nested']['value'], "\n"; -echo $persistentFirst->name, "\n"; -echo $persistentFirst->rows[12]['label'], "\n"; -echo $persistentFirst->rows[12]['nested']['value'], "\n"; -echo $persistentSecond->name, "\n"; -echo $persistentSecond->rows[12]['label'], "\n"; -echo $persistentSecond->rows[12]['nested']['value'], "\n"; +echo $pinnedFirst->name, "\n"; +echo $pinnedFirst->rows[12]['label'], "\n"; +echo $pinnedFirst->rows[12]['nested']['value'], "\n"; +echo $pinnedSecond->name, "\n"; +echo $pinnedSecond->rows[12]['label'], "\n"; +echo $pinnedSecond->rows[12]['nested']['value'], "\n"; ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_explicit_cache_request_local_object_copy_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_request_local_object_copy_001.phpt index 038a95600fd8..674ebceddb23 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_request_local_object_copy_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_request_local_object_copy_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- OPcache\persistent_fetch($key), + static fn (string $key): mixed => OPcache\pinned_fetch($key), ); ?> @@ -130,16 +130,16 @@ volatile-datetime-first-after-second-mutate=2026-05-16T12:34:56+09:00:volatile-d bool(false) volatile-datetime-third=2026-05-15T12:34:56+09:00:volatile-stored bool(false) -persistent-second-after-first-mutate=persistent-stored -persistent-first-after-second-mutate=persistent-first-mutated +pinned-second-after-first-mutate=pinned-stored +pinned-first-after-second-mutate=pinned-first-mutated bool(false) -persistent-third=persistent-stored +pinned-third=pinned-stored bool(false) -persistent-clone-hook=0:persistent-stored:persistent-stored +pinned-clone-hook=0:pinned-stored:pinned-stored bool(false) -persistent-datetime-clone-hook=0:persistent-stored:persistent-stored -persistent-datetime-second-before=2026-05-15T12:34:56+09:00 -persistent-datetime-second-after-first-mutate=2026-05-15T12:34:56+09:00:persistent-stored -persistent-datetime-first-after-second-mutate=2026-05-16T12:34:56+09:00:persistent-datetime-first-mutated +pinned-datetime-clone-hook=0:pinned-stored:pinned-stored +pinned-datetime-second-before=2026-05-15T12:34:56+09:00 +pinned-datetime-second-after-first-mutate=2026-05-15T12:34:56+09:00:pinned-stored +pinned-datetime-first-after-second-mutate=2026-05-16T12:34:56+09:00:pinned-datetime-first-mutated bool(false) -persistent-datetime-third=2026-05-15T12:34:56+09:00:persistent-stored +pinned-datetime-third=2026-05-15T12:34:56+09:00:pinned-stored diff --git a/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt index e580dfe9fc32..83b9b334b942 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt @@ -7,7 +7,7 @@ spl opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- --EXPECT-- @@ -124,17 +124,17 @@ volatile-collection-clone-calls=0 volatile-collection-second=volatile-collection-stored:1 volatile-collection-second-is-first=no volatile-collection-clone-calls=0 -persistent-stored -persistent-date-same-object=no -persistent-date-shared-peer=no -persistent-date-clone-calls=0 -persistent-date-second=2026-01-01:persistent-stored -persistent-date-second-is-first=no -persistent-date-clone-calls=0 -persistent-stored -persistent-collection-same-object=no -persistent-collection-shared-peer=no -persistent-collection-clone-calls=0 -persistent-collection-second=persistent-collection-stored:1 -persistent-collection-second-is-first=no -persistent-collection-clone-calls=0 +pinned-stored +pinned-date-same-object=no +pinned-date-shared-peer=no +pinned-date-clone-calls=0 +pinned-date-second=2026-01-01:pinned-stored +pinned-date-second-is-first=no +pinned-date-clone-calls=0 +pinned-stored +pinned-collection-same-object=no +pinned-collection-shared-peer=no +pinned-collection-clone-calls=0 +pinned-collection-second=pinned-collection-stored:1 +pinned-collection-second-is-first=no +pinned-collection-clone-calls=0 diff --git a/ext/opcache/tests/static_cache_explicit_cache_request_local_slot_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_request_local_slot_001.phpt index e7d188a51eeb..a31dc4a1e671 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_request_local_slot_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_request_local_slot_001.phpt @@ -7,7 +7,7 @@ spl opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- new ExplicitRequestLocalSlotPayload('volatile-stored')]; var_dump(OPcache\volatile_store('request_local_slot', $volatilePayload)); @@ -82,22 +82,22 @@ $volatileListSecond = OPcache\volatile_fetch('request_local_unsupported_internal var_dump($volatileListFirst === $volatileListSecond); echo $volatileListSecond->count(), ':', $volatileListSecond->bottom(), "\n"; -OPcache\persistent_store('request_local_slot', ['object' => new ExplicitRequestLocalSlotPayload('persistent-stored')]); -$persistentFirst = OPcache\persistent_fetch('request_local_slot'); -$persistentWarm = OPcache\persistent_fetch('request_local_slot'); -var_dump($persistentFirst['object'] === $persistentWarm['object']); -$persistentFirst['object']->label = 'persistent-mutated-in-request'; -$persistentSecond = OPcache\persistent_fetch('request_local_slot'); +OPcache\pinned_store('request_local_slot', ['object' => new ExplicitRequestLocalSlotPayload('pinned-stored')]); +$pinnedFirst = OPcache\pinned_fetch('request_local_slot'); +$pinnedWarm = OPcache\pinned_fetch('request_local_slot'); +var_dump($pinnedFirst['object'] === $pinnedWarm['object']); +$pinnedFirst['object']->label = 'pinned-mutated-in-request'; +$pinnedSecond = OPcache\pinned_fetch('request_local_slot'); -var_dump($persistentFirst['object'] === $persistentSecond['object']); -dump_label('persistent-second', $persistentSecond['object']); +var_dump($pinnedFirst['object'] === $pinnedSecond['object']); +dump_label('pinned-second', $pinnedSecond['object']); -OPcache\persistent_store('request_local_slot', ['object' => new ExplicitRequestLocalSlotPayload('persistent-replaced')]); -$persistentReplaced = OPcache\persistent_fetch('request_local_slot'); -dump_label('persistent-replaced', $persistentReplaced['object']); +OPcache\pinned_store('request_local_slot', ['object' => new ExplicitRequestLocalSlotPayload('pinned-replaced')]); +$pinnedReplaced = OPcache\pinned_fetch('request_local_slot'); +dump_label('pinned-replaced', $pinnedReplaced['object']); -OPcache\persistent_delete('request_local_slot'); -var_dump(OPcache\persistent_fetch('request_local_slot', 'persistent-missing')); +OPcache\pinned_delete('request_local_slot'); +var_dump(OPcache\pinned_fetch('request_local_slot', 'pinned-missing')); ?> --EXPECT-- @@ -125,6 +125,6 @@ bool(false) 1:stored bool(false) bool(false) -persistent-second=persistent-stored -persistent-replaced=persistent-replaced -string(18) "persistent-missing" +pinned-second=pinned-stored +pinned-replaced=pinned-replaced +string(14) "pinned-missing" diff --git a/ext/opcache/tests/static_cache_explicit_cache_shared_graph_array_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_shared_graph_array_001.phpt index f9ccc3867918..bc0c5ebd7e44 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_shared_graph_array_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_shared_graph_array_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- metadata['flags']['auth']); var_dump($copy['nodes'][1]->metadata['flags']['cache']); -OPcache\persistent_store('relocatable-array-object', new RelocatableArrayNode([ +OPcache\pinned_store('relocatable-array-object', new RelocatableArrayNode([ 'matrix' => [[1, 2], [3, 4]], ])); -$global = OPcache\persistent_fetch('relocatable-array-object'); +$global = OPcache\pinned_fetch('relocatable-array-object'); var_dump($global instanceof RelocatableArrayNode); var_dump($global->metadata['matrix'][1][0]); diff --git a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt index 204ec2269d83..ff829069c341 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt @@ -42,14 +42,14 @@ foreach ([ 'OPcache\\volatile_lock' => ['lease'], 'OPcache\\volatile_unlock' => [], 'OPcache\\volatile_cache_info' => [], - 'OPcache\\persistent_store' => ['value'], - 'OPcache\\persistent_fetch' => ['default'], - 'OPcache\\persistent_fetch_array' => ['default'], - 'OPcache\\persistent_lock' => ['lease'], - 'OPcache\\persistent_unlock' => [], - 'OPcache\\persistent_atomic_increment' => ['step'], - 'OPcache\\persistent_atomic_decrement' => ['step'], - 'OPcache\\persistent_cache_info' => [], + 'OPcache\\pinned_store' => ['value'], + 'OPcache\\pinned_fetch' => ['default'], + 'OPcache\\pinned_fetch_array' => ['default'], + 'OPcache\\pinned_lock' => ['lease'], + 'OPcache\\pinned_unlock' => [], + 'OPcache\\pinned_atomic_increment' => ['step'], + 'OPcache\\pinned_atomic_decrement' => ['step'], + 'OPcache\\pinned_cache_info' => [], ] as $function => $parameters) { $reflection = new ReflectionFunction($function); $parts = [$function]; @@ -74,11 +74,11 @@ OPcache\volatile_fetch_array $default=?array params=1/2 return=?array OPcache\volatile_lock $lease=int params=1/2 return=bool OPcache\volatile_unlock params=1/1 return=bool OPcache\volatile_cache_info params=0/0 return=OPcache\StaticCacheInfo -OPcache\persistent_store $value=null|bool|int|float|string|array|object params=2/2 return=void -OPcache\persistent_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object -OPcache\persistent_fetch_array $default=?array params=1/2 return=?array -OPcache\persistent_lock $lease=int params=1/2 return=bool -OPcache\persistent_unlock params=1/1 return=bool -OPcache\persistent_atomic_increment $step=int params=1/2 return=int -OPcache\persistent_atomic_decrement $step=int params=1/2 return=int -OPcache\persistent_cache_info params=0/0 return=OPcache\StaticCacheInfo +OPcache\pinned_store $value=null|bool|int|float|string|array|object params=2/2 return=void +OPcache\pinned_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object +OPcache\pinned_fetch_array $default=?array params=1/2 return=?array +OPcache\pinned_lock $lease=int params=1/2 return=bool +OPcache\pinned_unlock params=1/1 return=bool +OPcache\pinned_atomic_increment $step=int params=1/2 return=int +OPcache\pinned_atomic_decrement $step=int params=1/2 return=int +OPcache\pinned_cache_info params=0/0 return=OPcache\StaticCacheInfo diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt index 95444eba2554..67dc0cdb394e 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_store_array_invalid_key_001.phpt @@ -6,16 +6,16 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- ['OPcache\\volatile_store_array', 'OPcache\\volatile_fetch'], - 'persistent' => ['OPcache\\persistent_store_array', 'OPcache\\persistent_fetch'], + 'pinned' => ['OPcache\\pinned_store_array', 'OPcache\\pinned_fetch'], ] as $label => [$storeArray, $fetch]) { $key = $label . '_first'; @@ -35,5 +35,5 @@ foreach ([ --EXPECT-- volatile: OPcache\volatile_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names string(7) "missing" -persistent: OPcache\persistent_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names +pinned: OPcache\pinned_store_array(): Argument #1 ($values) must be an array with non-empty string keys that are not reserved static-cache class keys or loaded class names string(7) "missing" diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_delete_fork_reuse_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_delete_fork_reuse_001.phpt index 324ceebb59a9..668d983ed960 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_store_delete_fork_reuse_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_store_delete_fork_reuse_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache explicit volatile and persistent delete frees payload memory across processes +OPcache explicit volatile and pinned delete frees payload memory across processes --EXTENSIONS-- opcache pcntl @@ -13,7 +13,7 @@ if (!function_exists('pcntl_fork')) { opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=8 -opcache.static_cache.persistent_size_mb=8 +opcache.static_cache.pinned_size_mb=8 --FILE-- --EXPECT-- @@ -98,7 +98,7 @@ bool(true) int(1800000) int(1800000) int(1500000) --- persistent -- +-- pinned -- bool(true) bool(true) bool(true) diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_delete_request_reuse_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_delete_request_reuse_001.phpt index 916d1621780b..d404049c47af 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_store_delete_request_reuse_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_store_delete_request_reuse_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache explicit volatile and persistent delete frees payload memory across requests +OPcache explicit volatile and pinned delete frees payload memory across requests --EXTENSIONS-- opcache --CONFLICTS-- @@ -15,7 +15,7 @@ function cache_clear(string $kind): void if ($kind === 'volatile') { OPcache\volatile_clear(); } else { - OPcache\persistent_clear(); + OPcache\pinned_clear(); } } @@ -25,7 +25,7 @@ function cache_store(string $kind, string $key, string $value): bool return OPcache\volatile_store($key, $value); } - OPcache\persistent_store($key, $value); + OPcache\pinned_store($key, $value); return true; } @@ -33,7 +33,7 @@ function cache_fetch(string $kind, string $key): string { return $kind === 'volatile' ? OPcache\volatile_fetch($key, 'missing') - : OPcache\persistent_fetch($key, 'missing'); + : OPcache\pinned_fetch($key, 'missing'); } function cache_delete(string $kind, string $key): void @@ -41,7 +41,7 @@ function cache_delete(string $kind, string $key): void if ($kind === 'volatile') { OPcache\volatile_delete($key); } else { - OPcache\persistent_delete($key); + OPcache\pinned_delete($key); } } @@ -85,10 +85,10 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=8 -d opcache.static_cache.persistent_size_mb=8 -d opcache.file_update_protection=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=8 -d opcache.static_cache.pinned_size_mb=8 -d opcache.file_update_protection=0'); $base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/explicit_cache_store_delete_request_reuse_001.php'; -foreach (['volatile', 'persistent'] as $kind) { +foreach (['volatile', 'pinned'] as $kind) { echo "-- {$kind} --\n"; echo file_get_contents($base . '?kind=' . $kind . '&action=seed'); echo file_get_contents($base . '?kind=' . $kind . '&action=delete'); @@ -111,7 +111,7 @@ bool(true) int(1800000) int(1800000) int(1500000) --- persistent -- +-- pinned -- bool(true) bool(true) bool(true) diff --git a/ext/opcache/tests/static_cache_explicit_cache_store_delete_zts_threads_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_store_delete_zts_threads_001.phpt index ab3fa6fd11c0..612785622ae2 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_store_delete_zts_threads_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_store_delete_zts_threads_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache explicit volatile and persistent delete frees payload memory across threads +OPcache explicit volatile and pinned delete frees payload memory across threads --EXTENSIONS-- opcache --SKIPIF-- diff --git a/ext/opcache/tests/static_cache_explicit_cache_unlock_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_unlock_001.phpt index a7ba2b6e36f3..a57f0a2462ef 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_unlock_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_unlock_001.phpt @@ -6,7 +6,7 @@ opcache opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- 1]); - var_dump(OPcache\persistent_exists('shared')); + OPcache\pinned_store('shared', ['v' => 1]); + var_dump(OPcache\pinned_exists('shared')); return; } -var_dump(OPcache\persistent_exists('shared')); -var_dump(OPcache\persistent_fetch('shared', 'fallback')); -var_dump(OPcache\persistent_fetch('missing')); -var_dump(OPcache\persistent_fetch('missing', 'fallback')); +var_dump(OPcache\pinned_exists('shared')); +var_dump(OPcache\pinned_fetch('shared', 'fallback')); +var_dump(OPcache\pinned_fetch('missing')); +var_dump(OPcache\pinned_fetch('missing', 'fallback')); -OPcache\persistent_store('null', null); -var_dump(OPcache\persistent_fetch('null', 'fallback')); +OPcache\pinned_store('null', null); +var_dump(OPcache\pinned_fetch('null', 'fallback')); PHP); $php = getenv('TEST_PHP_EXECUTABLE'); @@ -33,15 +33,15 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_cache_api_001.php?action=write'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_cache_api_001.php?action=read'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_cache_api_001.php?action=write'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_cache_api_001.php?action=read'); ?> --CLEAN-- --EXPECT-- bool(true) diff --git a/ext/opcache/tests/static_cache_persistent_cache_array_mutation_overflow_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_array_mutation_overflow_001.phpt index 793209aed14c..bf7bc912a1d7 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_array_mutation_overflow_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_array_mutation_overflow_001.phpt @@ -1,29 +1,29 @@ --TEST-- -OPcache PersistentStatic throws an exception when array mutation exhausts persistent cache shared memory +OPcache PinnedStatic throws an exception when array mutation exhausts pinned cache shared memory --EXTENSIONS-- opcache --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.static_cache.persistent_size_mb=8 +opcache.static_cache.pinned_size_mb=8 opcache.optimization_level=0 opcache.file_update_protection=0 opcache.jit=0 --FILE-- getMessage(), "\n"; - OPcache\persistent_clear(); + OPcache\pinned_clear(); } ?> diff --git a/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt index 1132cfb1b8ee..820ea0d93729 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt @@ -1,32 +1,32 @@ --TEST-- -OPcache persistent atomic increment and decrement create missing keys +OPcache pinned atomic increment and decrement create missing keys --EXTENSIONS-- opcache --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- getMessage(), "\n"; } try { - OPcache\persistent_atomic_decrement('text'); + OPcache\pinned_atomic_decrement('text'); } catch (StaticCacheException $exception) { echo $exception->getMessage(), "\n"; } diff --git a/ext/opcache/tests/static_cache_persistent_cache_batch_atomic_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_batch_atomic_001.phpt index 04fcfe2124c7..eefe5a03a77e 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_batch_atomic_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_batch_atomic_001.phpt @@ -1,15 +1,15 @@ --TEST-- -OPcache persistent cache batch and atomic public API +OPcache pinned cache batch and atomic public API --EXTENSIONS-- opcache --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- 10, 'name' => 'php', 'null' => null, @@ -17,15 +17,15 @@ OPcache\persistent_store_array([ $fallback = ['fallback']; -var_dump(OPcache\persistent_fetch_array(['count', 'name', 'missing'], $fallback)); -var_dump(OPcache\persistent_atomic_increment('count')); -var_dump(OPcache\persistent_atomic_decrement('count', 3)); +var_dump(OPcache\pinned_fetch_array(['count', 'name', 'missing'], $fallback)); +var_dump(OPcache\pinned_atomic_increment('count')); +var_dump(OPcache\pinned_atomic_decrement('count', 3)); -OPcache\persistent_delete_array(['name', 'missing']); -var_dump(OPcache\persistent_fetch_array(['count', 'name', 'null'], $fallback)); +OPcache\pinned_delete_array(['name', 'missing']); +var_dump(OPcache\pinned_fetch_array(['count', 'name', 'null'], $fallback)); -OPcache\persistent_clear(); -var_dump(OPcache\persistent_fetch_array(['count'], $fallback)); +OPcache\pinned_clear(); +var_dump(OPcache\pinned_fetch_array(['count'], $fallback)); ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_persistent_cache_clear_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_clear_001.phpt index 8838a2a07297..cf36256ba191 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_clear_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_clear_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache persistent_clear clears only the persistent cache API namespace +OPcache pinned_clear clears only the pinned cache API namespace --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,28 +7,28 @@ server --FILE-- --CLEAN-- --EXPECT-- bool(true) string(14) "volatile-value" -string(18) "missing-persistent" +string(14) "missing-pinned" string(14) "volatile-value" -string(18) "missing-persistent" +string(14) "missing-pinned" diff --git a/ext/opcache/tests/static_cache_persistent_cache_clear_combined_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_clear_combined_001.phpt index c8c9d1e16dd1..8de24c49a013 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_clear_combined_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_clear_combined_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache persistent_clear drops combined persistent entries without corrupting refill or volatile entries +OPcache pinned_clear drops combined pinned entries without corrupting refill or volatile entries --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,10 +7,10 @@ server --FILE-- rows[123]['text']); return; } if ($action === 'clear') { - OPcache\persistent_clear(); + OPcache\pinned_clear(); var_dump(strlen(OPcache\volatile_fetch('local-string', 'missing-volatile'))); - var_dump(OPcache\persistent_fetch('persistent-string', 'missing-persistent')); - var_dump(OPcache\persistent_fetch('persistent-graph', 'missing-graph')); + var_dump(OPcache\pinned_fetch('pinned-string', 'missing-pinned')); + var_dump(OPcache\pinned_fetch('pinned-graph', 'missing-graph')); return; } if ($action === 'refill') { - OPcache\persistent_store('persistent-string', str_repeat('H', 400000)); - OPcache\persistent_store('persistent-graph', build_graph('N', 7)); - $graph = OPcache\persistent_fetch('persistent-graph'); - var_dump(strlen(OPcache\persistent_fetch('persistent-string'))); + OPcache\pinned_store('pinned-string', str_repeat('H', 400000)); + OPcache\pinned_store('pinned-graph', build_graph('N', 7)); + $graph = OPcache\pinned_fetch('pinned-graph'); + var_dump(strlen(OPcache\pinned_fetch('pinned-string'))); var_dump($graph->rows[123]['text']); var_dump($graph->rows[123]['nested']['value']); return; } -$graph = OPcache\persistent_fetch('persistent-graph'); +$graph = OPcache\pinned_fetch('pinned-graph'); var_dump(strlen(OPcache\volatile_fetch('local-string', 'missing-volatile'))); -var_dump(strlen(OPcache\persistent_fetch('persistent-string'))); +var_dump(strlen(OPcache\pinned_fetch('pinned-string'))); var_dump($graph->rows[123]['text']); var_dump($graph->rows[123]['nested']['value']); PHP); @@ -81,9 +81,9 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=8 -d opcache.static_cache.persistent_size_mb=8 -d opcache.file_update_protection=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=8 -d opcache.static_cache.pinned_size_mb=8 -d opcache.file_update_protection=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_cache_clear_combined_001.php'; +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_cache_clear_combined_001.php'; echo file_get_contents($base . '?action=seed'); echo file_get_contents($base . '?action=clear'); echo file_get_contents($base . '?action=refill'); @@ -92,14 +92,14 @@ echo file_get_contents($base . '?action=read'); ?> --CLEAN-- --EXPECT-- bool(true) int(400000) string(32) "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO" int(400000) -string(18) "missing-persistent" +string(14) "missing-pinned" string(13) "missing-graph" int(400000) string(32) "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN" diff --git a/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt index f63ab81d7897..06995ee675a4 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_info_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache volatile_cache_info and persistent_cache_info report separate cache backends +OPcache volatile_cache_info and pinned_cache_info report separate cache backends --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,10 +7,10 @@ server --FILE-- entry_count, ',', $persistentInfo->entry_count, ',', OPcache\volatile_fetch('explicit-volatile-key'), ',', ++PersistentInfoCounter::$value, "\n"; +echo $volatileInfo->entry_count, ',', $pinnedInfo->entry_count, ',', OPcache\volatile_fetch('explicit-volatile-key'), ',', ++PinnedInfoCounter::$value, "\n"; var_dump($status['volatile_cache'] == $volatileInfo); -var_dump($status['persistent_cache'] == $persistentInfo); +var_dump($status['pinned_cache'] == $pinnedInfo); var_dump($config['directives']['opcache.static_cache.volatile_size_mb']); -var_dump($config['directives']['opcache.static_cache.persistent_size_mb']); +var_dump($config['directives']['opcache.static_cache.pinned_size_mb']); PHP); $php = getenv('TEST_PHP_EXECUTABLE'); @@ -41,15 +41,15 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_cache_info_001.php?action=write'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_cache_info_001.php?action=info'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_cache_info_001.php?action=write'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_cache_info_001.php?action=info'); ?> --CLEAN-- --EXPECT-- 1 diff --git a/ext/opcache/tests/static_cache_persistent_cache_lock_atomic_increment_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_lock_atomic_increment_001.phpt index 0f557af2ee34..26998513e47b 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_lock_atomic_increment_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_lock_atomic_increment_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache persistent_lock is released by persistent_atomic_increment +OPcache pinned_lock is released by pinned_atomic_increment --EXTENSIONS-- opcache pcntl @@ -12,7 +12,7 @@ if (!function_exists('pcntl_fork')) { --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- diff --git a/ext/opcache/tests/static_cache_persistent_cache_overflow_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_overflow_001.phpt index 37cb7bcab62e..bd5484c48793 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_overflow_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_overflow_001.phpt @@ -1,28 +1,28 @@ --TEST-- -OPcache PersistentStatic throws an exception when property assignment exhausts persistent cache shared memory +OPcache PinnedStatic throws an exception when property assignment exhausts pinned cache shared memory --EXTENSIONS-- opcache --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.static_cache.persistent_size_mb=8 +opcache.static_cache.pinned_size_mb=8 opcache.optimization_level=0 opcache.file_update_protection=0 opcache.jit=0 --FILE-- getMessage(), "\n"; - OPcache\persistent_clear(); + OPcache\pinned_clear(); } ?> diff --git a/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt index e58ca6a82307..2130b74fd82c 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt @@ -1,26 +1,26 @@ --TEST-- -OPcache persistent_store throws StaticCacheException when persistent cache memory is exhausted +OPcache pinned_store throws StaticCacheException when pinned cache memory is exhausted --EXTENSIONS-- opcache --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.static_cache.persistent_size_mb=8 +opcache.static_cache.pinned_size_mb=8 --FILE-- getMessage(), "\n"; } -var_dump(OPcache\persistent_fetch('small', 'fallback')); -var_dump(OPcache\persistent_fetch('missing', 'fallback')); +var_dump(OPcache\pinned_fetch('small', 'fallback')); +var_dump(OPcache\pinned_fetch('missing', 'fallback')); ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_persistent_static_array_mutation_fast_path_001.phpt b/ext/opcache/tests/static_cache_persistent_static_array_mutation_fast_path_001.phpt index ca6ad6ff1976..177b882fdf91 100644 --- a/ext/opcache/tests/static_cache_persistent_static_array_mutation_fast_path_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_array_mutation_fast_path_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic publishes class, property, and method array mutations immediately +OPcache PinnedStatic publishes class, property, and method array mutations immediately --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,23 +7,23 @@ server --FILE-- --CLEAN-- --EXPECT-- reset diff --git a/ext/opcache/tests/static_cache_persistent_static_array_mutation_jit_001.phpt b/ext/opcache/tests/static_cache_persistent_static_array_mutation_jit_001.phpt index de81f6623d37..6ce650f3f192 100644 --- a/ext/opcache/tests/static_cache_persistent_static_array_mutation_jit_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_array_mutation_jit_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic array mutations remain visible through tracing JIT static slot reads +OPcache PinnedStatic array mutations remain visible through tracing JIT static slot reads --EXTENSIONS-- opcache --CONFLICTS-- @@ -13,22 +13,22 @@ if (!array_key_exists('opcache.jit', opcache_get_configuration()['directives'])) --FILE-- --CLEAN-- --EXPECT-- reset diff --git a/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt b/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt index 188a0ae14388..7c440e39d39e 100644 --- a/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_attribute_scope_separation_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic keeps class, property, and method attribute scopes separate +OPcache PinnedStatic keeps class, property, and method attribute scopes separate --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,9 +7,9 @@ server --FILE-- entry_count, "\n"; + OPcache\pinned_cache_info()->entry_count, "\n"; return; } @@ -85,17 +85,17 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_010.php?action=reset'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_010.php?action=step'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_010.php?action=step'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_010.php?action=inspect'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_010.php?action=reset'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_010.php?action=step'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_010.php?action=step'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_010.php?action=inspect'); ?> --CLEAN-- --EXPECT-- reset diff --git a/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt b/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt index 32499e4a0755..7a25123bd8c7 100644 --- a/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_capture_miss_001.phpt @@ -7,7 +7,7 @@ server --FILE-- CaptureMissCachedMethod::touch(), - 'persistent' => CaptureMissPersistentMethod::touch(), + 'pinned' => CaptureMissPinnedMethod::touch(), default => throw new RuntimeException('unknown backend'), }; @@ -63,18 +63,18 @@ if ($action === 'miss_then_explicit_mutate') { echo 'explicit_bag=', count($explicit->bag), "\n"; echo 'volatile_entries=', OPcache\volatile_cache_info()->entry_count, "\n"; - echo 'persistent_entries=', OPcache\persistent_cache_info()->entry_count, "\n"; + echo 'pinned_entries=', OPcache\pinned_cache_info()->entry_count, "\n"; return; } if ($action === 'read_static') { match ($backend) { 'cached' => CaptureMissCachedMethod::touch(), - 'persistent' => CaptureMissPersistentMethod::touch(), + 'pinned' => CaptureMissPinnedMethod::touch(), default => throw new RuntimeException('unknown backend'), }; echo 'volatile_entries=', OPcache\volatile_cache_info()->entry_count, "\n"; - echo 'persistent_entries=', OPcache\persistent_cache_info()->entry_count, "\n"; + echo 'pinned_entries=', OPcache\pinned_cache_info()->entry_count, "\n"; return; } @@ -88,10 +88,10 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_capture_miss_001.php'; -foreach (['cached', 'persistent'] as $backend) { +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_capture_miss_001.php'; +foreach (['cached', 'pinned'] as $backend) { echo file_get_contents($base . '?action=reset'); echo file_get_contents($base . '?action=seed'); echo file_get_contents($base . '?action=miss_then_explicit_mutate&backend=' . $backend); @@ -101,7 +101,7 @@ foreach (['cached', 'persistent'] as $backend) { ?> --CLEAN-- --EXPECT-- reset @@ -109,14 +109,14 @@ bool(true) volatile_entries=1 explicit_bag=2 volatile_entries=1 -persistent_entries=0 +pinned_entries=0 volatile_entries=1 -persistent_entries=0 +pinned_entries=0 reset bool(true) volatile_entries=1 explicit_bag=2 volatile_entries=1 -persistent_entries=0 +pinned_entries=0 volatile_entries=1 -persistent_entries=0 +pinned_entries=0 diff --git a/ext/opcache/tests/static_cache_persistent_static_capture_tracking_001.phpt b/ext/opcache/tests/static_cache_persistent_static_capture_tracking_001.phpt index 32c1010f9870..be950b27e71f 100644 --- a/ext/opcache/tests/static_cache_persistent_static_capture_tracking_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_capture_tracking_001.phpt @@ -7,7 +7,7 @@ server --FILE-- [CaptureTrackingCachedMethodA::value(), CaptureTrackingCachedMethodB::value()], 'cached_property' => [CaptureTrackingCachedPropertyA::value(), CaptureTrackingCachedPropertyB::value()], - 'persistent_method' => [CaptureTrackingPersistentMethodA::value(), CaptureTrackingPersistentMethodB::value()], - 'persistent_property' => [CaptureTrackingPersistentPropertyA::value(), CaptureTrackingPersistentPropertyB::value()], + 'pinned_method' => [CaptureTrackingPinnedMethodA::value(), CaptureTrackingPinnedMethodB::value()], + 'pinned_property' => [CaptureTrackingPinnedPropertyA::value(), CaptureTrackingPinnedPropertyB::value()], default => throw new RuntimeException('unknown kind'), }; } @@ -206,10 +206,10 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_capture_tracking_001.php'; -foreach (['cached_method', 'cached_property', 'persistent_method', 'persistent_property'] as $kind) { +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_capture_tracking_001.php'; +foreach (['cached_method', 'cached_property', 'pinned_method', 'pinned_property'] as $kind) { echo file_get_contents($base . '?action=reset'); echo file_get_contents($base . '?action=seed&kind=' . $kind); echo file_get_contents($base . '?action=mutate_after_fetch&kind=' . $kind); @@ -219,7 +219,7 @@ foreach (['cached_method', 'cached_property', 'persistent_method', 'persistent_p ?> --CLEAN-- --EXPECT-- reset @@ -231,10 +231,10 @@ cached_property:1,2,1|1,2,1 cached_property:2,3,2|2,3,2 cached_property:2,3,2|2,3,2 reset -persistent_method:1,2,1|1,2,1 -persistent_method:1,2,1|1,2,1 -persistent_method:0,1,0|0,1,0 +pinned_method:1,2,1|1,2,1 +pinned_method:1,2,1|1,2,1 +pinned_method:0,1,0|0,1,0 reset -persistent_property:1,2,1|1,2,1 -persistent_property:1,2,1|1,2,1 -persistent_property:0,1,0|0,1,0 +pinned_property:1,2,1|1,2,1 +pinned_property:1,2,1|1,2,1 +pinned_property:0,1,0|0,1,0 diff --git a/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt index 9f49f372dbfe..47b68948e799 100644 --- a/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_class_blob_dynamic_method_statics_jit_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic class blob survives dynamic method statics with JIT enabled +OPcache PinnedStatic class blob survives dynamic method statics with JIT enabled --EXTENSIONS-- opcache --CONFLICTS-- @@ -13,20 +13,20 @@ if (!array_key_exists('opcache.jit', opcache_get_configuration()['directives'])) --FILE-- entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; + echo pinned_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\pinned_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; $last = 0; for ($i = 0; $i < 4; $i++) { @@ -72,7 +72,7 @@ if ($request === 2) { return; } -echo persistent_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\persistent_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; +echo pinned_static_jit_on(), ',', JitCombinedBlobState::$count, ',', array_sum(JitCombinedBlobState::$bag['numbers']), ',', OPcache\pinned_cache_info()->entry_count, ',', count(file($logFile, FILE_IGNORE_NEW_LINES)), "\n"; PHP); $php = getenv('TEST_PHP_EXECUTABLE'); @@ -81,20 +81,20 @@ if ($php) { putenv('TEST_PHP_EXECUTABLE=' . $php); } -@unlink(__DIR__ . '/persistent_static_007.log'); +@unlink(__DIR__ . '/pinned_static_007.log'); include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=tracing -d opcache.jit_buffer_size=64M -d opcache.jit_hot_loop=0 -d opcache.jit_hot_func=2 -d opcache.jit_hot_return=0 -d opcache.jit_hot_side_exit=1'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=tracing -d opcache.jit_buffer_size=64M -d opcache.jit_hot_loop=0 -d opcache.jit_hot_func=2 -d opcache.jit_hot_return=0 -d opcache.jit_hot_side_exit=1'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_007.php?request=1'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_007.php?request=2'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_007.php?request=3'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_007.php?request=1'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_007.php?request=2'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_007.php?request=3'); ?> --CLEAN-- --EXPECT-- 1,7,6,21,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_class_blob_readonly_skip_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_blob_readonly_skip_001.phpt index a7ea7b5f4c37..14177e4a7b3f 100644 --- a/ext/opcache/tests/static_cache_persistent_static_class_blob_readonly_skip_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_class_blob_readonly_skip_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic skips class blob publish for read-only cached requests +OPcache PinnedStatic skips class blob publish for read-only cached requests --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,7 +7,7 @@ server --FILE-- --CLEAN-- --EXPECT-- reset diff --git a/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt index bd7d7c57fad0..e33b1e5929f4 100644 --- a/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_class_blob_single_key_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic class attribute stores class-wide state under one cache key +OPcache PinnedStatic class attribute stores class-wide state under one cache key --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,9 +7,9 @@ server --FILE-- entry_count, "\n"; +echo CombinedBlobState::$propertyCount, ',', CombinedBlobState::$propertyBag['numbers'][0], ',', CombinedBlobState::nextMethod(), ',', OPcache\pinned_cache_info()->entry_count, "\n"; CombinedBlobState::$propertyCount++; CombinedBlobState::$propertyBag['numbers'][] = 11; @@ -45,15 +45,15 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.pinned_size_mb=32'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_006.php?request=1'); -echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_006.php?request=2'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_006.php?request=1'); +echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_006.php?request=2'); ?> --CLEAN-- --EXPECT-- 1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_class_property_persistence_001.phpt b/ext/opcache/tests/static_cache_persistent_static_class_property_persistence_001.phpt index b08738ee1db1..5e2ed7de02f0 100644 --- a/ext/opcache/tests/static_cache_persistent_static_class_property_persistence_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_class_property_persistence_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic persists class static properties across requests +OPcache PinnedStatic persists class static properties across requests --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,9 +7,9 @@ server --FILE-- --CLEAN-- --EXPECT-- 1,1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_complex_value_persistence_001.phpt b/ext/opcache/tests/static_cache_persistent_static_complex_value_persistence_001.phpt index 04ccf180b7d5..7ddc4fc32cf0 100644 --- a/ext/opcache/tests/static_cache_persistent_static_complex_value_persistence_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_complex_value_persistence_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic persists complex static values across requests +OPcache PinnedStatic persists complex static values across requests --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,14 +7,14 @@ server --FILE-- --CLEAN-- --EXPECT-- 2,4,101:5 diff --git a/ext/opcache/tests/static_cache_persistent_static_disabled_without_persistent_cache_memory_001.phpt b/ext/opcache/tests/static_cache_persistent_static_disabled_without_persistent_cache_memory_001.phpt index 8f1f2a970153..480c494a033a 100644 --- a/ext/opcache/tests/static_cache_persistent_static_disabled_without_persistent_cache_memory_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_disabled_without_persistent_cache_memory_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic stays disabled when persistent cache memory is 0 +OPcache PinnedStatic stays disabled when pinned cache memory is 0 --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,9 +7,9 @@ server --FILE-- --CLEAN-- --EXPECT-- 1 diff --git a/ext/opcache/tests/static_cache_persistent_static_inherited_attributes_001.phpt b/ext/opcache/tests/static_cache_persistent_static_inherited_attributes_001.phpt index 6df412bbe026..c24dd665f688 100644 --- a/ext/opcache/tests/static_cache_persistent_static_inherited_attributes_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_inherited_attributes_001.phpt @@ -7,10 +7,10 @@ server --FILE-- --CLEAN-- --EXPECT-- 1,1,1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_invalidate_001.phpt b/ext/opcache/tests/static_cache_persistent_static_invalidate_001.phpt index 8456d6830471..f65113e4953e 100644 --- a/ext/opcache/tests/static_cache_persistent_static_invalidate_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_invalidate_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic state is deleted only for the invalidated script +OPcache PinnedStatic state is deleted only for the invalidated script --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,13 +7,13 @@ server --FILE-- --CLEAN-- --EXPECT-- 1,1,1,1 @@ -97,6 +97,6 @@ echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_ 2 bool(true) keep -keep-persistent +keep-pinned 1,1,1,1 3 diff --git a/ext/opcache/tests/static_cache_persistent_static_invalidate_after_access_001.phpt b/ext/opcache/tests/static_cache_persistent_static_invalidate_after_access_001.phpt index 473787fc02ed..a18c10562fdc 100644 --- a/ext/opcache/tests/static_cache_persistent_static_invalidate_after_access_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_invalidate_after_access_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic is not republished after opcache_invalidate() in the same request +OPcache PinnedStatic is not republished after opcache_invalidate() in the same request --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,9 +7,9 @@ server --FILE-- --CLEAN-- --EXPECT-- 1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_local_copy_array_mutation_publish_001.phpt b/ext/opcache/tests/static_cache_persistent_static_local_copy_array_mutation_publish_001.phpt index b79b4d337efe..90fcfa2d6218 100644 --- a/ext/opcache/tests/static_cache_persistent_static_local_copy_array_mutation_publish_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_local_copy_array_mutation_publish_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic skips nested array local-copy mutation publish +OPcache PinnedStatic skips nested array local-copy mutation publish --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,9 +7,9 @@ server --FILE-- ['seed'], - 'probe' => new PersistentStaticLocalCopyPublishProbe($logFile, 42), + 'probe' => new PinnedStaticLocalCopyPublishProbe($logFile, 42), ]; - echo 'seed-static=', count(PersistentStaticLocalCopyArrayState::$value['nested']), "\n"; + echo 'seed-static=', count(PinnedStaticLocalCopyArrayState::$value['nested']), "\n"; echo 'seed-log=', local_copy_log_count(), "\n"; return; } if ($action === 'local') { - $copy = PersistentStaticLocalCopyArrayState::$value['nested']; + $copy = PinnedStaticLocalCopyArrayState::$value['nested']; $copy[] = 'local'; echo 'local-copy=', count($copy), "\n"; - echo 'local-static=', count(PersistentStaticLocalCopyArrayState::$value['nested']), "\n"; + echo 'local-static=', count(PinnedStaticLocalCopyArrayState::$value['nested']), "\n"; echo 'local-log=', local_copy_log_count(), "\n"; return; } if ($action === 'direct') { - PersistentStaticLocalCopyArrayState::$value['nested'][] = 'direct'; - echo 'direct-static=', count(PersistentStaticLocalCopyArrayState::$value['nested']), "\n"; + PinnedStaticLocalCopyArrayState::$value['nested'][] = 'direct'; + echo 'direct-static=', count(PinnedStaticLocalCopyArrayState::$value['nested']), "\n"; echo 'direct-log=', local_copy_log_count(), "\n"; return; } -echo 'read-static=', count(PersistentStaticLocalCopyArrayState::$value['nested']), "\n"; +echo 'read-static=', count(PinnedStaticLocalCopyArrayState::$value['nested']), "\n"; echo 'read-log=', local_copy_log_count(), "\n"; PHP); @@ -94,12 +94,12 @@ if ($php) { putenv('TEST_PHP_EXECUTABLE=' . $php); } -@unlink(__DIR__ . '/persistent_static_local_copy_array_mutation_publish_001.log'); +@unlink(__DIR__ . '/pinned_static_local_copy_array_mutation_publish_001.log'); include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.pinned_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_local_copy_array_mutation_publish_001.php'; +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_local_copy_array_mutation_publish_001.php'; echo file_get_contents($base . '?action=reset'); echo file_get_contents($base . '?action=seed'); echo file_get_contents($base . '?action=local'); @@ -110,8 +110,8 @@ echo file_get_contents($base . '?action=read'); ?> --CLEAN-- --EXPECT-- reset diff --git a/ext/opcache/tests/static_cache_persistent_static_method_static_persistence_001.phpt b/ext/opcache/tests/static_cache_persistent_static_method_static_persistence_001.phpt index f6bdb65374a6..898e6d33a061 100644 --- a/ext/opcache/tests/static_cache_persistent_static_method_static_persistence_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_method_static_persistence_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic persists method static variables across requests +OPcache PinnedStatic persists method static variables across requests --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,15 +7,15 @@ server --FILE-- --CLEAN-- --EXPECT-- 1,2,1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_object_assignment_fast_path_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_assignment_fast_path_001.phpt index 858fb5101e7b..480f4dc7ec99 100644 --- a/ext/opcache/tests/static_cache_persistent_static_object_assignment_fast_path_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_object_assignment_fast_path_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic class object assignments publish a snapshot without following later object mutations +OPcache PinnedStatic class object assignments publish a snapshot without following later object mutations --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,31 +7,31 @@ server --FILE-- name = 'mutated'; - echo 'global-in-request=', PersistentStaticAssignmentFastPathState::$payload->name, "\n"; + PinnedStaticAssignmentFastPathState::$payload = new PinnedStaticAssignmentFastPathPayload('assigned'); + PinnedStaticAssignmentFastPathState::$payload->name = 'mutated'; + echo 'global-in-request=', PinnedStaticAssignmentFastPathState::$payload->name, "\n"; return; } if ($action === 'read-global') { - echo 'global-after=', PersistentStaticAssignmentFastPathState::$payload?->name ?? 'missing', "\n"; + echo 'global-after=', PinnedStaticAssignmentFastPathState::$payload?->name ?? 'missing', "\n"; return; } if ($action === 'seed-global-property') { - PersistentStaticPropertyAssignmentFastPathState::$payload = new PersistentStaticAssignmentFastPathPayload('assigned'); - PersistentStaticPropertyAssignmentFastPathState::$payload->name = 'mutated'; - echo 'global-property-in-request=', PersistentStaticPropertyAssignmentFastPathState::$payload->name, "\n"; + PinnedStaticPropertyAssignmentFastPathState::$payload = new PinnedStaticAssignmentFastPathPayload('assigned'); + PinnedStaticPropertyAssignmentFastPathState::$payload->name = 'mutated'; + echo 'global-property-in-request=', PinnedStaticPropertyAssignmentFastPathState::$payload->name, "\n"; return; } if ($action === 'read-global-property') { - echo 'global-property-after=', PersistentStaticPropertyAssignmentFastPathState::$payload?->name ?? 'missing', "\n"; + echo 'global-property-after=', PinnedStaticPropertyAssignmentFastPathState::$payload?->name ?? 'missing', "\n"; return; } if ($action === 'seed-cached') { - VolatileStaticAssignmentTrackedState::$payload = new PersistentStaticAssignmentFastPathPayload('assigned'); + VolatileStaticAssignmentTrackedState::$payload = new PinnedStaticAssignmentFastPathPayload('assigned'); VolatileStaticAssignmentTrackedState::$payload->name = 'mutated'; echo 'cached-in-request=', VolatileStaticAssignmentTrackedState::$payload->name, "\n"; return; @@ -83,9 +83,9 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_object_assignment_fast_path_001.php'; +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_object_assignment_fast_path_001.php'; echo file_get_contents($base . '?action=reset'); echo file_get_contents($base . '?action=seed-global'); echo file_get_contents($base . '?action=read-global'); @@ -99,7 +99,7 @@ echo file_get_contents($base . '?action=read-cached'); ?> --CLEAN-- --EXPECT-- reset diff --git a/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt index 67f797c45a01..0c2a5498b1d0 100644 --- a/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_object_assignment_snapshot_001.phpt @@ -1,11 +1,11 @@ --TEST-- -OPcache PersistentStatic snapshots object assignments without following object property writes +OPcache PinnedStatic snapshots object assignments without following object property writes --EXTENSIONS-- opcache --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 opcache.optimization_level=0 opcache.file_update_protection=0 opcache.jit=0 @@ -14,21 +14,21 @@ opcache.jit=0 class NestedObjectPropertyState { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static ?stdClass $propertyState = null; } NestedObjectPropertyState::$propertyState ??= (object) ['count' => 1]; var_dump(NestedObjectPropertyState::$propertyState->count); -var_dump(OPcache\persistent_cache_info()->entry_count); +var_dump(OPcache\pinned_cache_info()->entry_count); NestedObjectPropertyState::$propertyState->count++; var_dump(NestedObjectPropertyState::$propertyState->count); -var_dump(OPcache\persistent_cache_info()->entry_count); +var_dump(OPcache\pinned_cache_info()->entry_count); NestedObjectPropertyState::$propertyState->count = 10; var_dump(NestedObjectPropertyState::$propertyState->count); -var_dump(OPcache\persistent_cache_info()->entry_count); +var_dump(OPcache\pinned_cache_info()->entry_count); ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_persistent_static_object_dim_mutation_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_dim_mutation_001.phpt index 5e5186b3dd06..c41d342249a4 100644 --- a/ext/opcache/tests/static_cache_persistent_static_object_dim_mutation_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_object_dim_mutation_001.phpt @@ -8,7 +8,7 @@ server --FILE-- VolatileStaticArrayObjectMethodState::value(), - 'persistent_static' => PersistentStaticArrayObjectMethodState::value(), - 'persistent_static_property' => PersistentStaticArrayObjectPropertyState::value(), + 'pinned_static' => PinnedStaticArrayObjectMethodState::value(), + 'pinned_static_property' => PinnedStaticArrayObjectPropertyState::value(), default => throw new RuntimeException('unknown backend'), }; @@ -79,10 +79,10 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_object_dim_mutation_001.php'; -foreach (['volatile_static', 'persistent_static', 'persistent_static_property'] as $backend) { +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_object_dim_mutation_001.php'; +foreach (['volatile_static', 'pinned_static', 'pinned_static_property'] as $backend) { $query = 'backend=' . $backend; echo file_get_contents($base . '?action=reset'); echo file_get_contents($base . '?action=read&' . $query); @@ -93,7 +93,7 @@ foreach (['volatile_static', 'persistent_static', 'persistent_static_property'] ?> --CLEAN-- --EXPECT-- reset @@ -101,10 +101,10 @@ volatile_static=0,0,1 volatile_static=1,1,0 volatile_static=1,1,0 reset -persistent_static=0,0,1 -persistent_static=1,1,0 -persistent_static=0,0,1 +pinned_static=0,0,1 +pinned_static=1,1,0 +pinned_static=0,0,1 reset -persistent_static_property=0,0,1 -persistent_static_property=1,1,0 -persistent_static_property=0,0,1 +pinned_static_property=0,0,1 +pinned_static_property=1,1,0 +pinned_static_property=0,0,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_object_property_mutation_001.phpt b/ext/opcache/tests/static_cache_persistent_static_object_property_mutation_001.phpt index 13c167e2555a..5737354695f5 100644 --- a/ext/opcache/tests/static_cache_persistent_static_object_property_mutation_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_object_property_mutation_001.phpt @@ -7,7 +7,7 @@ server --FILE-- VolatileStaticPlainMethodState::value(), 'volatile_static:safe_direct' => VolatileStaticSafeDirectMethodState::value(), - 'persistent_static:plain' => PersistentStaticPlainMethodState::value(), - 'persistent_static:safe_direct' => PersistentStaticSafeDirectMethodState::value(), - 'persistent_static_property:plain' => PersistentStaticPlainPropertyState::value(), - 'persistent_static_property:safe_direct' => PersistentStaticSafeDirectPropertyState::value(), + 'pinned_static:plain' => PinnedStaticPlainMethodState::value(), + 'pinned_static:safe_direct' => PinnedStaticSafeDirectMethodState::value(), + 'pinned_static_property:plain' => PinnedStaticPlainPropertyState::value(), + 'pinned_static_property:safe_direct' => PinnedStaticSafeDirectPropertyState::value(), default => throw new RuntimeException('unknown backend or kind'), }; @@ -126,10 +126,10 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_object_property_mutation_001.php'; -foreach (['volatile_static', 'persistent_static', 'persistent_static_property'] as $backend) { +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_object_property_mutation_001.php'; +foreach (['volatile_static', 'pinned_static', 'pinned_static_property'] as $backend) { foreach (['plain', 'safe_direct'] as $kind) { $query = 'backend=' . $backend . '&kind=' . $kind; echo file_get_contents($base . '?action=reset'); @@ -142,7 +142,7 @@ foreach (['volatile_static', 'persistent_static', 'persistent_static_property'] ?> --CLEAN-- --EXPECT-- reset @@ -154,18 +154,18 @@ volatile_static/safe_direct=0 volatile_static/safe_direct=1 volatile_static/safe_direct=1 reset -persistent_static/plain=0 -persistent_static/plain=1 -persistent_static/plain=0 +pinned_static/plain=0 +pinned_static/plain=1 +pinned_static/plain=0 reset -persistent_static/safe_direct=0 -persistent_static/safe_direct=1 -persistent_static/safe_direct=0 +pinned_static/safe_direct=0 +pinned_static/safe_direct=1 +pinned_static/safe_direct=0 reset -persistent_static_property/plain=0 -persistent_static_property/plain=1 -persistent_static_property/plain=0 +pinned_static_property/plain=0 +pinned_static_property/plain=1 +pinned_static_property/plain=0 reset -persistent_static_property/safe_direct=0 -persistent_static_property/safe_direct=1 -persistent_static_property/safe_direct=0 +pinned_static_property/safe_direct=0 +pinned_static_property/safe_direct=1 +pinned_static_property/safe_direct=0 diff --git a/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt b/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt index fd162399a7c3..9caf9da2882a 100644 --- a/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_property_array_mutation_publish_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic publishes property array mutations immediately +OPcache PinnedStatic publishes property array mutations immediately --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,18 +7,18 @@ server --FILE-- [1]]; NestedSnapshotPropertyState::$propertyState['numbers'][] = 2; echo 'seed=', implode(',', NestedSnapshotPropertyState::$propertyState['numbers']), "\n"; - echo 'entries=', OPcache\persistent_cache_info()->entry_count, "\n"; + echo 'entries=', OPcache\pinned_cache_info()->entry_count, "\n"; return; } @@ -48,9 +48,9 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.persistent_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.pinned_size_mb=32 -d opcache.optimization_level=0 -d opcache.file_update_protection=0 -d opcache.jit=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_004.php'; +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_004.php'; echo file_get_contents($base . '?action=reset'); echo file_get_contents($base . '?action=seed'); echo file_get_contents($base . '?action=read'); @@ -60,7 +60,7 @@ echo file_get_contents($base . '?action=read'); ?> --CLEAN-- --EXPECT-- reset diff --git a/ext/opcache/tests/static_cache_persistent_static_readonly_publish_001.phpt b/ext/opcache/tests/static_cache_persistent_static_readonly_publish_001.phpt index 0af6ed85bb4f..f3ccf72b4aa0 100644 --- a/ext/opcache/tests/static_cache_persistent_static_readonly_publish_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_readonly_publish_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache VolatileStatic tracking skips read-only shutdown publish like PersistentStatic +OPcache VolatileStatic tracking skips read-only shutdown publish like PinnedStatic --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,7 +7,7 @@ server --FILE-- CachedReadOnlyMethodState::value($logFile), 'cached_property' => CachedReadOnlyPropertyState::value($logFile), - 'persistent_method' => PersistentReadOnlyMethodState::value($logFile), - 'persistent_property' => PersistentReadOnlyPropertyState::value($logFile), + 'pinned_method' => PinnedReadOnlyMethodState::value($logFile), + 'pinned_property' => PinnedReadOnlyPropertyState::value($logFile), default => throw new RuntimeException('Unknown kind: ' . $kind), }; @@ -131,16 +131,16 @@ if ($php) { putenv('TEST_PHP_EXECUTABLE=' . $php); } -foreach (['cached_method', 'cached_property', 'persistent_method', 'persistent_property'] as $kind) { - @unlink(__DIR__ . '/persistent_static_readonly_publish_001_' . $kind . '.log'); +foreach (['cached_method', 'cached_property', 'pinned_method', 'pinned_property'] as $kind) { + @unlink(__DIR__ . '/pinned_static_readonly_publish_001_' . $kind . '.log'); } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0'); -$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/persistent_static_readonly_publish_001.php'; +$base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/pinned_static_readonly_publish_001.php'; echo file_get_contents($base . '?action=reset'); -foreach (['cached_method', 'cached_property', 'persistent_method', 'persistent_property'] as $kind) { +foreach (['cached_method', 'cached_property', 'pinned_method', 'pinned_property'] as $kind) { echo file_get_contents($base . '?action=value&kind=' . $kind); echo file_get_contents($base . '?action=count&kind=' . $kind); echo file_get_contents($base . '?action=value&kind=' . $kind); @@ -150,9 +150,9 @@ foreach (['cached_method', 'cached_property', 'persistent_method', 'persistent_p ?> --CLEAN-- --EXPECT-- @@ -165,11 +165,11 @@ cached_property=17 cached_property_count=1 cached_property=17 cached_property_count=1 -persistent_method=13 -persistent_method_count=1 -persistent_method=13 -persistent_method_count=1 -persistent_property=19 -persistent_property_count=1 -persistent_property=19 -persistent_property_count=1 +pinned_method=13 +pinned_method_count=1 +pinned_method=13 +pinned_method_count=1 +pinned_property=19 +pinned_property_count=1 +pinned_property=19 +pinned_property_count=1 diff --git a/ext/opcache/tests/static_cache_persistent_static_recursive_shared_graph_snapshot_001.phpt b/ext/opcache/tests/static_cache_persistent_static_recursive_shared_graph_snapshot_001.phpt index 43f2fae5d74f..a59a4b9f4713 100644 --- a/ext/opcache/tests/static_cache_persistent_static_recursive_shared_graph_snapshot_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_recursive_shared_graph_snapshot_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic snapshots recursive shared-graph state across requests +OPcache PinnedStatic snapshots recursive shared-graph state across requests --EXTENSIONS-- opcache spl @@ -8,7 +8,7 @@ server --FILE-- --CLEAN-- --EXPECT-- 2,1,11,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_reset_001.phpt b/ext/opcache/tests/static_cache_persistent_static_reset_001.phpt index 0479b9bb1195..bb505c90f56a 100644 --- a/ext/opcache/tests/static_cache_persistent_static_reset_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_reset_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic and explicit static caches are deleted by opcache_reset() +OPcache PinnedStatic and explicit static caches are deleted by opcache_reset() --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,12 +7,12 @@ server --FILE-- --CLEAN-- --EXPECT-- 1,1,1,1 2,2,2,2 bool(true) missing-volatile -missing-persistent +missing-pinned 1,1,1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_reset_after_access_001.phpt b/ext/opcache/tests/static_cache_persistent_static_reset_after_access_001.phpt index 006b1b05031b..5f8ceed862c9 100644 --- a/ext/opcache/tests/static_cache_persistent_static_reset_after_access_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_reset_after_access_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic is not republished after opcache_reset() in the same request +OPcache PinnedStatic is not republished after opcache_reset() in the same request --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,9 +7,9 @@ server --FILE-- --CLEAN-- --EXPECT-- 1,1 diff --git a/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt b/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt index af6b64e2258c..903c227a97ee 100644 --- a/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt @@ -1,11 +1,11 @@ --TEST-- -OPcache PersistentStatic storage failures throw StaticCacheException +OPcache PinnedStatic storage failures throw StaticCacheException --EXTENSIONS-- opcache --INI-- opcache.enable=1 opcache.enable_cli=1 -opcache.static_cache.persistent_size_mb=8 +opcache.static_cache.pinned_size_mb=8 opcache.optimization_level=0 opcache.file_update_protection=0 opcache.jit=0 @@ -22,21 +22,21 @@ function dump_static_cache_exception(string $label, Closure $callback): void } } -class PersistentStaticStoreFailureProperty +class PinnedStaticStoreFailureProperty { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static mixed $value = null; } -#[OPcache\PersistentStatic] -class PersistentStaticStoreFailureClass +#[OPcache\PinnedStatic] +class PinnedStaticStoreFailureClass { public static mixed $value = null; } -class PersistentStaticStoreFailureMethod +class PinnedStaticStoreFailureMethod { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static function assign(mixed $value): void { static $state = null; @@ -45,25 +45,25 @@ class PersistentStaticStoreFailureMethod } } -class PersistentStaticStoreFailureArray +class PinnedStaticStoreFailureArray { - #[OPcache\PersistentStatic] + #[OPcache\PinnedStatic] public static array $value = []; } -class PersistentStaticStoreFailureUnsupportedValueBox +class PinnedStaticStoreFailureUnsupportedValueBox { public function __construct(public mixed $value) { } } -OPcache\persistent_clear(); +OPcache\pinned_clear(); dump_static_cache_exception('property-resource', function (): void { $resource = fopen('/dev/null', 'r'); try { - PersistentStaticStoreFailureProperty::$value = $resource; + PinnedStaticStoreFailureProperty::$value = $resource; } finally { if (is_resource($resource)) { fclose($resource); @@ -72,13 +72,13 @@ dump_static_cache_exception('property-resource', function (): void { }); dump_static_cache_exception('property-closure', function (): void { - PersistentStaticStoreFailureProperty::$value = static fn () => null; + PinnedStaticStoreFailureProperty::$value = static fn () => null; }); dump_static_cache_exception('property-object-resource', function (): void { $resource = fopen('/dev/null', 'r'); try { - PersistentStaticStoreFailureProperty::$value = new PersistentStaticStoreFailureUnsupportedValueBox($resource); + PinnedStaticStoreFailureProperty::$value = new PinnedStaticStoreFailureUnsupportedValueBox($resource); } finally { if (is_resource($resource)) { fclose($resource); @@ -87,24 +87,24 @@ dump_static_cache_exception('property-object-resource', function (): void { }); dump_static_cache_exception('property-object-closure', function (): void { - PersistentStaticStoreFailureProperty::$value = new PersistentStaticStoreFailureUnsupportedValueBox(static fn () => null); + PinnedStaticStoreFailureProperty::$value = new PinnedStaticStoreFailureUnsupportedValueBox(static fn () => null); }); -$unused = PersistentStaticStoreFailureClass::$value; +$unused = PinnedStaticStoreFailureClass::$value; dump_static_cache_exception('class-closure', function (): void { - PersistentStaticStoreFailureClass::$value = static fn () => null; + PinnedStaticStoreFailureClass::$value = static fn () => null; }); dump_static_cache_exception('method-closure', function (): void { - PersistentStaticStoreFailureMethod::assign(static fn () => null); + PinnedStaticStoreFailureMethod::assign(static fn () => null); }); -PersistentStaticStoreFailureArray::$value = []; +PinnedStaticStoreFailureArray::$value = []; dump_static_cache_exception('array-mutation-overflow', function (): void { - PersistentStaticStoreFailureArray::$value[] = str_repeat('X', 12 * 1024 * 1024); + PinnedStaticStoreFailureArray::$value[] = str_repeat('X', 12 * 1024 * 1024); }); -OPcache\persistent_clear(); +OPcache\pinned_clear(); ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_persistent_static_timestamp_invalidate_001.phpt b/ext/opcache/tests/static_cache_persistent_static_timestamp_invalidate_001.phpt index 3990d880a945..c7930d266fe4 100644 --- a/ext/opcache/tests/static_cache_persistent_static_timestamp_invalidate_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_timestamp_invalidate_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic state is discarded when a cached class definition changes +OPcache PinnedStatic state is discarded when a cached class definition changes --EXTENSIONS-- opcache --CONFLICTS-- @@ -7,12 +7,12 @@ server --FILE-- --CLEAN-- --EXPECT-- v1:1 diff --git a/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt b/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt index 05d970de4407..142870c217aa 100644 --- a/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache PersistentStatic class, property, and method state handles cross-thread writes on ZTS builds +OPcache PinnedStatic class, property, and method state handles cross-thread writes on ZTS builds --EXTENSIONS-- opcache --SKIPIF-- @@ -223,9 +223,9 @@ if ($root === false) { } $buildRoot = resolveBuildRoot($root); -$source = __DIR__ . '/helpers/persistent_static_zts_threads_001.c'; -$binary = __DIR__ . '/helpers/persistent_static_zts_threads_001' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); -$scenario = __DIR__ . '/helpers/persistent_static_zts_threads_001.inc'; +$source = __DIR__ . '/helpers/pinned_static_zts_threads_001.c'; +$binary = __DIR__ . '/helpers/pinned_static_zts_threads_001' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); +$scenario = __DIR__ . '/helpers/pinned_static_zts_threads_001.inc'; $compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); $buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); $extraLibs = resolveExtraLibs($buildRoot); @@ -293,10 +293,10 @@ if ($status !== 0) { ?> --CLEAN-- --EXPECT-- ok diff --git a/ext/opcache/tests/static_cache_preload_001.inc b/ext/opcache/tests/static_cache_preload_001.inc index 0578cbf21703..39ff5d12477d 100644 --- a/ext/opcache/tests/static_cache_preload_001.inc +++ b/ext/opcache/tests/static_cache_preload_001.inc @@ -1,6 +1,6 @@ true]; diff --git a/ext/opcache/tests/static_cache_preload_001.phpt b/ext/opcache/tests/static_cache_preload_001.phpt index 990e70f7620e..f9c256d2585a 100644 --- a/ext/opcache/tests/static_cache_preload_001.phpt +++ b/ext/opcache/tests/static_cache_preload_001.phpt @@ -10,7 +10,7 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 opcache.preload={PWD}/static_cache_preload_001.inc --FILE-- enabled); var_dump($status['volatile_cache']->available); var_dump($status['volatile_cache']->startup_failed); var_dump($status['volatile_cache']->backend_initialized); var_dump($status['volatile_cache']->configured_memory); -var_dump($status['persistent_cache']->enabled); -var_dump($status['persistent_cache']->available); -var_dump($status['persistent_cache']->startup_failed); -var_dump($status['persistent_cache']->backend_initialized); -var_dump($status['persistent_cache']->configured_memory); +var_dump($status['pinned_cache']->enabled); +var_dump($status['pinned_cache']->available); +var_dump($status['pinned_cache']->startup_failed); +var_dump($status['pinned_cache']->backend_initialized); +var_dump($status['pinned_cache']->configured_memory); var_dump($volatileInfo == $status['volatile_cache']); -var_dump($persistentInfo == $status['persistent_cache']); +var_dump($pinnedInfo == $status['pinned_cache']); var_dump($volatileInfo->failure_reason); -var_dump($persistentInfo->failure_reason); +var_dump($pinnedInfo->failure_reason); try { OPcache\volatile_store('key', 'value'); @@ -38,7 +38,7 @@ try { } try { - OPcache\persistent_store('key', 'value'); + OPcache\pinned_store('key', 'value'); } catch (Throwable $e) { echo get_class($e), ': ', $e->getMessage(), "\n"; } diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt index 9c95f9f77954..2791756018df 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt @@ -7,7 +7,7 @@ spl opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- OPcache\persistent_store('fixed-resource', $fixed_array)); -dump_persistent_exception(static fn () => OPcache\persistent_store('array-object-closure', $array_object)); +dump_pinned_exception(static fn () => OPcache\pinned_store('fixed-resource', $fixed_array)); +dump_pinned_exception(static fn () => OPcache\pinned_store('array-object-closure', $array_object)); fclose($resource); diff --git a/ext/opcache/tests/static_cache_volatile_cache_no_atomic_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_no_atomic_001.phpt index 686cdce68681..6ff2f181b440 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_no_atomic_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_no_atomic_001.phpt @@ -11,8 +11,8 @@ opcache.static_cache.volatile_size_mb=32 var_dump(function_exists('OPcache\\volatile_atomic_increment')); var_dump(function_exists('OPcache\\volatile_atomic_decrement')); -var_dump(function_exists('OPcache\\persistent_atomic_increment')); -var_dump(function_exists('OPcache\\persistent_atomic_decrement')); +var_dump(function_exists('OPcache\\pinned_atomic_increment')); +var_dump(function_exists('OPcache\\pinned_atomic_decrement')); ?> --EXPECT-- diff --git a/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt index 1dae05460fac..ef9e00aa4320 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt @@ -7,7 +7,7 @@ spl opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- OPcache\volatile_fetch('missing', $resource)); dump_type_error(static fn () => OPcache\volatile_fetch('missing', $closure)); -dump_type_error(static fn () => OPcache\persistent_store('resource', $resource)); -dump_type_error(static fn () => OPcache\persistent_store('closure-value', $closure)); -dump_static_cache_exception(static fn () => OPcache\persistent_store_array(['nested-resource' => ['value' => $resource]])); -dump_static_cache_exception(static fn () => OPcache\persistent_store_array(['nested-closure' => ['value' => $closure]])); -dump_static_cache_exception(static fn () => OPcache\persistent_store('object-resource', $resource_object)); -dump_static_cache_exception(static fn () => OPcache\persistent_store('object-closure', $closure_object)); -dump_static_cache_exception(static fn () => OPcache\persistent_store('spl-resource', $resource_fixed_array)); -dump_static_cache_exception(static fn () => OPcache\persistent_store('spl-closure', $closure_fixed_array)); -dump_static_cache_exception(static fn () => OPcache\persistent_store('array-object-resource', $resource_array_object)); -dump_static_cache_exception(static fn () => OPcache\persistent_store('array-object-closure', $closure_array_object)); +dump_type_error(static fn () => OPcache\pinned_store('resource', $resource)); +dump_type_error(static fn () => OPcache\pinned_store('closure-value', $closure)); +dump_static_cache_exception(static fn () => OPcache\pinned_store_array(['nested-resource' => ['value' => $resource]])); +dump_static_cache_exception(static fn () => OPcache\pinned_store_array(['nested-closure' => ['value' => $closure]])); +dump_static_cache_exception(static fn () => OPcache\pinned_store('object-resource', $resource_object)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('object-closure', $closure_object)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('spl-resource', $resource_fixed_array)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('spl-closure', $closure_fixed_array)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('array-object-resource', $resource_array_object)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('array-object-closure', $closure_array_object)); StaticCacheUnsupportedSerializedPayload::$value = $resource; -dump_static_cache_exception(static fn () => OPcache\persistent_store('serialized-resource', $serialized_payload)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('serialized-resource', $serialized_payload)); StaticCacheUnsupportedSerializedPayload::$value = $closure; -dump_static_cache_exception(static fn () => OPcache\persistent_store('serialized-closure', $serialized_payload)); -dump_type_error(static fn () => OPcache\persistent_fetch('missing', $resource)); -dump_type_error(static fn () => OPcache\persistent_fetch('missing', $closure)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('serialized-closure', $serialized_payload)); +dump_type_error(static fn () => OPcache\pinned_fetch('missing', $resource)); +dump_type_error(static fn () => OPcache\pinned_fetch('missing', $closure)); fclose($resource); @@ -139,8 +139,8 @@ bool(false) string(7) "missing" OPcache\volatile_fetch(): Argument #2 ($default) must be of type object|array|string|int|float|bool|null, resource given OPcache\volatile_fetch(): Argument #2 ($default) must not be a Closure object -OPcache\persistent_store(): Argument #2 ($value) must be of type object|array|string|int|float|bool|null, resource given -OPcache\persistent_store(): Argument #2 ($value) must not be a Closure object +OPcache\pinned_store(): Argument #2 ($value) must be of type object|array|string|int|float|bool|null, resource given +OPcache\pinned_store(): Argument #2 ($value) must not be a Closure object OPcache\StaticCacheException: resources cannot be stored in the static cache OPcache\StaticCacheException: Closure objects cannot be stored in the static cache OPcache\StaticCacheException: resources cannot be stored in the static cache @@ -151,5 +151,5 @@ OPcache\StaticCacheException: resources and Closure objects cannot be stored in OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache OPcache\StaticCacheException: resources and Closure objects cannot be stored in the static cache -OPcache\persistent_fetch(): Argument #2 ($default) must be of type object|array|string|int|float|bool|null, resource given -OPcache\persistent_fetch(): Argument #2 ($default) must not be a Closure object +OPcache\pinned_fetch(): Argument #2 ($default) must be of type object|array|string|int|float|bool|null, resource given +OPcache\pinned_fetch(): Argument #2 ($default) must not be a Closure object diff --git a/ext/opcache/tests/static_cache_volatile_static_clear_isolated_from_persistent_static_001.phpt b/ext/opcache/tests/static_cache_volatile_static_clear_isolated_from_persistent_static_001.phpt index 4aa933f35298..50da7c0f25a5 100644 --- a/ext/opcache/tests/static_cache_volatile_static_clear_isolated_from_persistent_static_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_static_clear_isolated_from_persistent_static_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache VolatileStatic persists through volatile cache and can be cleared independently of PersistentStatic +OPcache VolatileStatic persists through volatile cache and can be cleared independently of PinnedStatic --EXTENSIONS-- opcache --CONFLICTS-- @@ -22,7 +22,7 @@ class CachedCounter } } -#[OPcache\PersistentStatic] +#[OPcache\PinnedStatic] class GlobalCounter { public static int $value = 0; @@ -52,7 +52,7 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0'); echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_001.php'); echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_001.php'); diff --git a/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt b/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt index 6d88779b3806..362c8969db22 100644 --- a/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_static_strategy_001.phpt @@ -202,7 +202,7 @@ $case = $_GET['case'] ?? 'class_default'; if ($action === 'reset') { OPcache\volatile_clear(); - OPcache\persistent_clear(); + OPcache\pinned_clear(); opcache_reset(); echo "reset\n"; return; @@ -219,7 +219,7 @@ if ($action === 'seed' || $action === 'mutate_after_fetch') { cached_strategy_dump($case, $values); if ($action === 'read') { - echo 'cache=', OPcache\volatile_cache_info()->entry_count, ',', OPcache\persistent_cache_info()->entry_count, "\n"; + echo 'cache=', OPcache\volatile_cache_info()->entry_count, ',', OPcache\pinned_cache_info()->entry_count, "\n"; } PHP); @@ -230,7 +230,7 @@ if ($php) { } include 'php_cli_server.inc'; -php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.persistent_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); +php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.static_cache.volatile_size_mb=32 -d opcache.static_cache.pinned_size_mb=32 -d opcache.file_update_protection=0 -d opcache.jit=0'); $base = 'http://' . PHP_CLI_SERVER_ADDRESS . '/volatile_static_strategy_001.php'; $cases = [ diff --git a/ext/opcache/tests/static_cache_windows_backend_001.phpt b/ext/opcache/tests/static_cache_windows_backend_001.phpt index 344361530bb8..8323db2ef24e 100644 --- a/ext/opcache/tests/static_cache_windows_backend_001.phpt +++ b/ext/opcache/tests/static_cache_windows_backend_001.phpt @@ -12,32 +12,32 @@ if (PHP_OS_FAMILY !== 'Windows') { opcache.enable=1 opcache.enable_cli=1 opcache.static_cache.volatile_size_mb=32 -opcache.static_cache.persistent_size_mb=32 +opcache.static_cache.pinned_size_mb=32 --FILE-- available); -var_dump($persistentInfo->available); +var_dump($pinnedInfo->available); var_dump($volatileInfo->shared_model); -var_dump($persistentInfo->shared_model); +var_dump($pinnedInfo->shared_model); var_dump($volatileInfo->shared_memory); -var_dump($persistentInfo->shared_memory); +var_dump($pinnedInfo->shared_memory); OPcache\volatile_clear(); -OPcache\persistent_clear(); +OPcache\pinned_clear(); $key = 'windows-backend'; var_dump(OPcache\volatile_lock($key)); var_dump(OPcache\volatile_store($key, ['backend' => 'volatile'])); -var_dump(OPcache\persistent_lock($key)); -OPcache\persistent_store($key, ['backend' => 'persistent']); +var_dump(OPcache\pinned_lock($key)); +OPcache\pinned_store($key, ['backend' => 'pinned']); var_dump(OPcache\volatile_fetch($key)); -var_dump(OPcache\persistent_fetch($key)); +var_dump(OPcache\pinned_fetch($key)); ?> --EXPECT-- @@ -56,5 +56,5 @@ array(1) { } array(1) { ["backend"]=> - string(10) "persistent" + string(10) "pinned" } diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index efa7e93ed77d..b10aad2a47c2 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -115,15 +115,15 @@ static ZEND_INI_MH(OnUpdateStaticCacheVolatileSizeMb) return SUCCESS; } -static ZEND_INI_MH(OnUpdateStaticCachePersistentSizeMb) +static ZEND_INI_MH(OnUpdateStaticCachePinnedSizeMb) { zend_long *p, memsize; if (accel_startup_ok) { if (strcmp(sapi_module.name, "fpm-fcgi") == 0) { - zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.persistent_size_mb cannot be changed when OPcache is already set up. Are you using php_admin_value[opcache.static_cache.persistent_size_mb] in an individual pool's configuration?\n"); + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.pinned_size_mb cannot be changed when OPcache is already set up. Are you using php_admin_value[opcache.static_cache.pinned_size_mb] in an individual pool's configuration?\n"); } else { - zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.persistent_size_mb cannot be changed when OPcache is already set up.\n"); + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.pinned_size_mb cannot be changed when OPcache is already set up.\n"); } return FAILURE; } @@ -131,7 +131,7 @@ static ZEND_INI_MH(OnUpdateStaticCachePersistentSizeMb) p = ZEND_INI_GET_ADDR(); memsize = atoi(ZSTR_VAL(new_value)); - /* zero disables the persistent cache */ + /* zero disables the pinned cache */ if (memsize == 0) { *p = 0; return SUCCESS; @@ -139,7 +139,7 @@ static ZEND_INI_MH(OnUpdateStaticCachePersistentSizeMb) /* sanity check we must use at least 8 MB */ if (memsize < 8) { - zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.persistent_size_mb is set below the required 8MB.\n"); + zend_accel_error(ACCEL_LOG_WARNING, "opcache.static_cache.pinned_size_mb is set below the required 8MB.\n"); return FAILURE; } @@ -370,8 +370,8 @@ ZEND_INI_BEGIN() STD_PHP_INI_ENTRY("opcache.log_verbosity_level" , "1" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.log_verbosity_level, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.memory_consumption" , "128" , PHP_INI_SYSTEM, OnUpdateMemoryConsumption, accel_directives.memory_consumption, zend_accel_globals, accel_globals) - STD_PHP_INI_ENTRY("opcache.static_cache.volatile_size_mb", "0", PHP_INI_SYSTEM, OnUpdateStaticCacheVolatileSizeMb, accel_directives.static_cache_volatile_size_mb, zend_accel_globals, accel_globals) - STD_PHP_INI_ENTRY("opcache.static_cache.persistent_size_mb", "0", PHP_INI_SYSTEM, OnUpdateStaticCachePersistentSizeMb, accel_directives.static_cache_persistent_size_mb, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.static_cache.volatile_size_mb", "8", PHP_INI_SYSTEM, OnUpdateStaticCacheVolatileSizeMb, accel_directives.static_cache_volatile_size_mb, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.static_cache.pinned_size_mb", "8", PHP_INI_SYSTEM, OnUpdateStaticCachePinnedSizeMb, accel_directives.static_cache_pinned_size_mb, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.interned_strings_buffer", "8" , PHP_INI_SYSTEM, OnUpdateInternedStringsBuffer, accel_directives.interned_strings_buffer, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.max_accelerated_files" , "10000", PHP_INI_SYSTEM, OnUpdateMaxAcceleratedFiles, accel_directives.max_accelerated_files, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.max_wasted_percentage" , "5" , PHP_INI_SYSTEM, OnUpdateMaxWastedPercentage, accel_directives.max_wasted_percentage, zend_accel_globals, accel_globals) @@ -601,14 +601,14 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS) php_info_print_table_row(2, "Volatile Static Cache Failure", zend_opcache_static_cache_volatile_failure_reason()); } } - if (!zend_opcache_static_cache_persistent_is_enabled()) { - php_info_print_table_row(2, "Persistent Static Cache", "Disabled"); - } else if (zend_opcache_static_cache_persistent_is_available()) { - php_info_print_table_row(2, "Persistent Static Cache", "Enabled"); + if (!zend_opcache_static_cache_pinned_is_enabled()) { + php_info_print_table_row(2, "Pinned Static Cache", "Disabled"); + } else if (zend_opcache_static_cache_pinned_is_available()) { + php_info_print_table_row(2, "Pinned Static Cache", "Enabled"); } else { - php_info_print_table_row(2, "Persistent Static Cache", "Unavailable"); - if (zend_opcache_static_cache_persistent_failure_reason()) { - php_info_print_table_row(2, "Persistent Static Cache Failure", zend_opcache_static_cache_persistent_failure_reason()); + php_info_print_table_row(2, "Pinned Static Cache", "Unavailable"); + if (zend_opcache_static_cache_pinned_failure_reason()) { + php_info_print_table_row(2, "Pinned Static Cache Failure", zend_opcache_static_cache_pinned_failure_reason()); } } @@ -778,7 +778,7 @@ static int accelerator_get_scripts(zval *return_value) ZEND_FUNCTION(opcache_get_status) { zend_long reqs; - zval memory_usage, statistics, scripts, volatile_cache, persistent_cache; + zval memory_usage, statistics, scripts, volatile_cache, pinned_cache; bool fetch_scripts = true; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &fetch_scripts) == FAILURE) { @@ -805,8 +805,8 @@ ZEND_FUNCTION(opcache_get_status) /* Static cache */ zend_opcache_static_cache_volatile_get_status(&volatile_cache); add_assoc_zval(return_value, "volatile_cache", &volatile_cache); - zend_opcache_static_cache_persistent_get_status(&persistent_cache); - add_assoc_zval(return_value, "persistent_cache", &persistent_cache); + zend_opcache_static_cache_pinned_get_status(&pinned_cache); + add_assoc_zval(return_value, "pinned_cache", &pinned_cache); if (file_cache_only) { add_assoc_bool(return_value, "file_cache_only", 1); @@ -943,7 +943,7 @@ ZEND_FUNCTION(opcache_get_configuration) add_assoc_long(&directives, "opcache.log_verbosity_level", ZCG(accel_directives).log_verbosity_level); add_assoc_long(&directives, "opcache.memory_consumption", ZCG(accel_directives).memory_consumption); add_assoc_long(&directives, "opcache.static_cache.volatile_size_mb", ZCG(accel_directives).static_cache_volatile_size_mb); - add_assoc_long(&directives, "opcache.static_cache.persistent_size_mb", ZCG(accel_directives).static_cache_persistent_size_mb); + add_assoc_long(&directives, "opcache.static_cache.pinned_size_mb", ZCG(accel_directives).static_cache_pinned_size_mb); add_assoc_long(&directives, "opcache.interned_strings_buffer",ZCG(accel_directives).interned_strings_buffer); add_assoc_long(&directives, "opcache.max_accelerated_files", ZCG(accel_directives).max_accelerated_files); add_assoc_double(&directives, "opcache.max_wasted_percentage", ZCG(accel_directives).max_wasted_percentage); diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index 89d3a532d4e7..d5533ece4fd0 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -45,7 +45,7 @@ zend_class_entry *zend_opcache_static_cache_exception_ce; zend_class_entry *zend_opcache_static_cache_strategy_ce; zend_class_entry *zend_opcache_static_cache_info_ce; -static zend_class_entry *zend_opcache_static_cache_persistent_attribute_ce; +static zend_class_entry *zend_opcache_static_cache_pinned_attribute_ce; static zend_class_entry *zend_opcache_static_cache_volatile_static_attribute_ce; static zend_class_entry *zend_opcache_static_cache_safe_direct_attribute_ce; @@ -61,10 +61,10 @@ zend_opcache_static_cache_context zend_opcache_static_cache_volatile_context_sta true, false }; -zend_opcache_static_cache_context zend_opcache_static_cache_persistent_context_state = { - {0}, {0}, "persistent cache", "opcache_persistent_cache_lock", +zend_opcache_static_cache_context zend_opcache_static_cache_pinned_context_state = { + {0}, {0}, "pinned cache", "opcache_pinned_cache_lock", #ifndef ZEND_WIN32 - ZEND_OPCACHE_STATIC_CACHE_PERSISTENT_SEM_FILENAME_PREFIX, + ZEND_OPCACHE_STATIC_CACHE_PINNED_SEM_FILENAME_PREFIX, #endif false, true }; @@ -88,7 +88,7 @@ ZEND_EXT_TLS bool zend_opcache_static_cache_function_statics_initialized = false ZEND_EXT_TLS HashTable zend_opcache_static_cache_class_blob_handles; ZEND_EXT_TLS bool zend_opcache_static_cache_class_blob_handles_initialized = false; ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_volatile_runtime_state = {0}; -ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_persistent_runtime_state = {0}; +ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_pinned_runtime_state = {0}; ZEND_EXT_TLS zend_opcache_static_cache_context *zend_opcache_static_cache_active_context_ptr = NULL; ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_roots = NULL; ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_references = NULL; @@ -97,7 +97,7 @@ ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_objects = NULL; ZEND_EXT_TLS bool zend_opcache_static_cache_has_tracked_arrays = false; ZEND_EXT_TLS bool zend_opcache_static_cache_has_tracked_objects = false; ZEND_EXT_TLS bool zend_opcache_static_cache_volatile_skip_publish = false; -ZEND_EXT_TLS bool zend_opcache_static_cache_persistent_skip_publish = false; +ZEND_EXT_TLS bool zend_opcache_static_cache_pinned_skip_publish = false; ZEND_EXT_TLS zend_opcache_static_cache_shared_graph_ref *zend_opcache_static_cache_shared_graph_refs = NULL; ZEND_EXT_TLS uint32_t zend_opcache_static_cache_shared_graph_ref_count = 0; ZEND_EXT_TLS uint32_t zend_opcache_static_cache_shared_graph_ref_capacity = 0; @@ -118,17 +118,17 @@ ZEND_EXT_TLS zend_opcache_static_cache_static_slot_handle *zend_opcache_static_c ZEND_EXT_TLS HashTable *zend_opcache_static_cache_capture_arrays = NULL; ZEND_EXT_TLS HashTable *zend_opcache_static_cache_capture_objects = NULL; ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_volatile_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; -ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_persistent_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; +ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_pinned_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; ZEND_EXT_TLS HashTable *zend_opcache_static_cache_volatile_request_local_slots = NULL; -ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_request_local_slots = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_pinned_request_local_slots = NULL; ZEND_EXT_TLS HashTable *zend_opcache_static_cache_volatile_entry_locks = NULL; -ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_entry_locks = NULL; +ZEND_EXT_TLS HashTable *zend_opcache_static_cache_pinned_entry_locks = NULL; ZEND_EXT_TLS uint32_t zend_opcache_static_cache_volatile_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; -ZEND_EXT_TLS uint32_t zend_opcache_static_cache_persistent_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; +ZEND_EXT_TLS uint32_t zend_opcache_static_cache_pinned_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; static zend_always_inline bool zend_opcache_static_cache_key_has_reserved_class_prefix(zend_string *key) { - return zend_string_starts_with_literal(key, "persistent_static_class:") || + return zend_string_starts_with_literal(key, "pinned_static_class:") || zend_string_starts_with_literal(key, "volatile_static_class:") ; } @@ -566,13 +566,13 @@ static zend_always_inline void zend_opcache_static_cache_register_classes(void) { zend_internal_attribute *attribute; - if (zend_opcache_static_cache_persistent_attribute_ce != NULL) { + if (zend_opcache_static_cache_pinned_attribute_ce != NULL) { return; } zend_opcache_static_cache_info_ce = register_class_OPcache_StaticCacheInfo(); - zend_opcache_static_cache_persistent_attribute_ce = register_class_OPcache_PersistentStatic(); - zend_mark_internal_attribute(zend_opcache_static_cache_persistent_attribute_ce); + zend_opcache_static_cache_pinned_attribute_ce = register_class_OPcache_PinnedStatic(); + zend_mark_internal_attribute(zend_opcache_static_cache_pinned_attribute_ce); zend_opcache_static_cache_strategy_ce = register_class_OPcache_CacheStrategy(); zend_opcache_static_cache_volatile_static_attribute_ce = register_class_OPcache_VolatileStatic(); attribute = zend_mark_internal_attribute(zend_opcache_static_cache_volatile_static_attribute_ce); @@ -1110,7 +1110,7 @@ static zend_always_inline void zend_opcache_static_cache_disable_subsystem(const previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); zend_opcache_static_cache_shutdown_storage(); zend_opcache_static_cache_reset_runtime(); - zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_shutdown_storage(); zend_opcache_static_cache_reset_runtime(); zend_opcache_static_cache_restore_context(previous_context); @@ -1136,7 +1136,7 @@ zend_result zend_opcache_static_cache_minit(void) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); zend_opcache_static_cache_reset_storage(); - zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_reset_storage(); zend_opcache_static_cache_restore_context(previous_context); @@ -1161,7 +1161,7 @@ static void zend_opcache_static_cache_startup(void) } } - zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_reset_runtime(); if (zend_opcache_static_cache_active_runtime()->enabled && ZCG(enabled) && accel_startup_ok && !file_cache_only) { if (!zend_opcache_static_cache_startup_storage_before_request()) { @@ -1178,7 +1178,7 @@ static void zend_opcache_static_cache_startup(void) if (!zend_opcache_static_cache_subsystem_disabled && ( ZCG(accel_directives).static_cache_volatile_size_mb != 0 || - ZCG(accel_directives).static_cache_persistent_size_mb != 0 + ZCG(accel_directives).static_cache_pinned_size_mb != 0 ) ) { zend_opcache_static_cache_register_hooks(); @@ -1207,7 +1207,7 @@ static void zend_opcache_static_cache_post_startup(void) } } - zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_reset_runtime(); if (!(zend_opcache_static_cache_active_context()->storage).initialized && zend_opcache_static_cache_active_runtime()->enabled && @@ -1229,7 +1229,7 @@ static void zend_opcache_static_cache_post_startup(void) if (!zend_opcache_static_cache_subsystem_disabled && ( ZCG(accel_directives).static_cache_volatile_size_mb != 0 || - ZCG(accel_directives).static_cache_persistent_size_mb != 0 + ZCG(accel_directives).static_cache_pinned_size_mb != 0 ) ) { zend_opcache_static_cache_safe_direct_register_internal_classes(); @@ -1245,7 +1245,7 @@ void zend_opcache_static_cache_mshutdown(void) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); zend_opcache_static_cache_shutdown_storage(); zend_opcache_static_cache_reset_runtime(); - zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_shutdown_storage(); zend_opcache_static_cache_reset_runtime(); zend_opcache_static_cache_restore_context(previous_context); @@ -1264,7 +1264,7 @@ static zend_result zend_opcache_static_cache_rinit(void) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); zend_opcache_static_cache_ensure_ready(); - zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_ensure_ready(); zend_opcache_static_cache_restore_context(previous_context); zend_opcache_static_cache_clear_lookup_caches(); @@ -1274,7 +1274,7 @@ static zend_result zend_opcache_static_cache_rinit(void) EG(tracked_mutation_hooks_active) = false; static_cache_available = zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_volatile_context_state)->available || - zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_persistent_context_state)->available + zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_pinned_context_state)->available ; if (!static_cache_available) { return SUCCESS; @@ -1304,7 +1304,7 @@ zend_result zend_opcache_static_cache_rshutdown(void) static void zend_opcache_static_cache_invalidate_script(zend_persistent_script *persistent_script) { - zend_opcache_static_cache_invalidate_script_context(&zend_opcache_static_cache_persistent_context_state, persistent_script); + zend_opcache_static_cache_invalidate_script_context(&zend_opcache_static_cache_pinned_context_state, persistent_script); zend_opcache_static_cache_invalidate_script_context(&zend_opcache_static_cache_volatile_context_state, persistent_script); } @@ -1322,7 +1322,7 @@ static void zend_opcache_static_cache_register_accelerator_handlers(void) void zend_opcache_static_cache_invalidate_all(void) { - zend_opcache_static_cache_invalidate_all_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_invalidate_all_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_invalidate_all_context(&zend_opcache_static_cache_volatile_context_state); } @@ -1334,9 +1334,9 @@ void zend_opcache_static_cache_volatile_get_status(zval *return_value) zend_opcache_static_cache_restore_context(previous_context); } -void zend_opcache_static_cache_persistent_get_status(zval *return_value) +void zend_opcache_static_cache_pinned_get_status(zval *return_value) { - zend_opcache_static_cache_context *previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_context *previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_populate_info(return_value); zend_opcache_static_cache_restore_context(previous_context); @@ -1357,19 +1357,19 @@ const char *zend_opcache_static_cache_volatile_failure_reason(void) return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_volatile_context_state)->failure_reason; } -bool zend_opcache_static_cache_persistent_is_enabled(void) +bool zend_opcache_static_cache_pinned_is_enabled(void) { - return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_persistent_context_state)->enabled; + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_pinned_context_state)->enabled; } -bool zend_opcache_static_cache_persistent_is_available(void) +bool zend_opcache_static_cache_pinned_is_available(void) { - return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_persistent_context_state)->available; + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_pinned_context_state)->available; } -const char *zend_opcache_static_cache_persistent_failure_reason(void) +const char *zend_opcache_static_cache_pinned_failure_reason(void) { - return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_persistent_context_state)->failure_reason; + return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_pinned_context_state)->failure_reason; } ZEND_METHOD(OPcache_VolatileStatic, __construct) @@ -1674,7 +1674,7 @@ ZEND_FUNCTION(OPcache_volatile_cache_info) zend_opcache_static_cache_restore_context(previous_context); } -ZEND_FUNCTION(OPcache_persistent_store) +ZEND_FUNCTION(OPcache_pinned_store) { zend_opcache_static_cache_context *previous_context; zend_string *key; @@ -1694,7 +1694,7 @@ ZEND_FUNCTION(OPcache_persistent_store) RETURN_THROWS(); } - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_validate_available_write()) { zend_opcache_static_cache_restore_context(previous_context); @@ -1709,7 +1709,7 @@ ZEND_FUNCTION(OPcache_persistent_store) } } -ZEND_FUNCTION(OPcache_persistent_store_array) +ZEND_FUNCTION(OPcache_pinned_store_array) { zend_opcache_static_cache_context *previous_context; zend_string *key; @@ -1720,7 +1720,7 @@ ZEND_FUNCTION(OPcache_persistent_store_array) Z_PARAM_ARRAY_HT(values) ZEND_PARSE_PARAMETERS_END(); - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_validate_available_write()) { zend_opcache_static_cache_restore_context(previous_context); @@ -1746,7 +1746,7 @@ ZEND_FUNCTION(OPcache_persistent_store_array) zend_opcache_static_cache_restore_context(previous_context); } -ZEND_FUNCTION(OPcache_persistent_fetch) +ZEND_FUNCTION(OPcache_pinned_fetch) { zend_string *key; zval *default_value = NULL, default_null; @@ -1770,12 +1770,12 @@ ZEND_FUNCTION(OPcache_persistent_fetch) RETURN_THROWS(); } - if (zend_opcache_static_cache_fetch_api(&zend_opcache_static_cache_persistent_context_state, key, default_value, return_value) == FAILURE) { + if (zend_opcache_static_cache_fetch_api(&zend_opcache_static_cache_pinned_context_state, key, default_value, return_value) == FAILURE) { RETURN_THROWS(); } } -ZEND_FUNCTION(OPcache_persistent_fetch_array) +ZEND_FUNCTION(OPcache_pinned_fetch_array) { HashTable *keys; zval *default_value = NULL, default_null; @@ -1791,12 +1791,12 @@ ZEND_FUNCTION(OPcache_persistent_fetch_array) default_value = &default_null; } - if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_persistent_context_state, keys, default_value, return_value) == FAILURE) { + if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_pinned_context_state, keys, default_value, return_value) == FAILURE) { RETURN_THROWS(); } } -ZEND_FUNCTION(OPcache_persistent_exists) +ZEND_FUNCTION(OPcache_pinned_exists) { zend_string *key; bool exists; @@ -1809,14 +1809,14 @@ ZEND_FUNCTION(OPcache_persistent_exists) RETURN_THROWS(); } - if (zend_opcache_static_cache_exists_api(&zend_opcache_static_cache_persistent_context_state, key, &exists) == FAILURE) { + if (zend_opcache_static_cache_exists_api(&zend_opcache_static_cache_pinned_context_state, key, &exists) == FAILURE) { RETURN_THROWS(); } RETURN_BOOL(exists); } -ZEND_FUNCTION(OPcache_persistent_lock) +ZEND_FUNCTION(OPcache_pinned_lock) { zend_string *key; zend_long lease = 0; @@ -1836,14 +1836,14 @@ ZEND_FUNCTION(OPcache_persistent_lock) RETURN_THROWS(); } - if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_persistent_context_state, key, lease, &locked) == FAILURE) { + if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_pinned_context_state, key, lease, &locked) == FAILURE) { RETURN_THROWS(); } RETURN_BOOL(locked); } -ZEND_FUNCTION(OPcache_persistent_unlock) +ZEND_FUNCTION(OPcache_pinned_unlock) { zend_string *key; bool unlocked; @@ -1856,14 +1856,14 @@ ZEND_FUNCTION(OPcache_persistent_unlock) RETURN_THROWS(); } - if (zend_opcache_static_cache_unlock_api(&zend_opcache_static_cache_persistent_context_state, key, &unlocked) == FAILURE) { + if (zend_opcache_static_cache_unlock_api(&zend_opcache_static_cache_pinned_context_state, key, &unlocked) == FAILURE) { RETURN_THROWS(); } RETURN_BOOL(unlocked); } -ZEND_FUNCTION(OPcache_persistent_delete) +ZEND_FUNCTION(OPcache_pinned_delete) { zend_opcache_static_cache_context *previous_context; zend_string *key_or_class; @@ -1876,7 +1876,7 @@ ZEND_FUNCTION(OPcache_persistent_delete) RETURN_THROWS(); } - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_validate_available_write()) { zend_opcache_static_cache_restore_context(previous_context); @@ -1891,7 +1891,7 @@ ZEND_FUNCTION(OPcache_persistent_delete) zend_opcache_static_cache_restore_context(previous_context); } -ZEND_FUNCTION(OPcache_persistent_delete_array) +ZEND_FUNCTION(OPcache_pinned_delete_array) { zend_opcache_static_cache_context *previous_context; zend_string **prepared_keys; @@ -1906,7 +1906,7 @@ ZEND_FUNCTION(OPcache_persistent_delete_array) RETURN_THROWS(); } - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_validate_available_write()) { zend_opcache_static_cache_restore_context(previous_context); zend_opcache_static_cache_release_key_list(prepared_keys, key_count); @@ -1927,13 +1927,13 @@ ZEND_FUNCTION(OPcache_persistent_delete_array) zend_opcache_static_cache_release_key_list(prepared_keys, key_count); } -ZEND_FUNCTION(OPcache_persistent_clear) +ZEND_FUNCTION(OPcache_pinned_clear) { zend_opcache_static_cache_context *previous_context; ZEND_PARSE_PARAMETERS_NONE(); - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_validate_available_write()) { zend_opcache_static_cache_restore_context(previous_context); @@ -1942,16 +1942,16 @@ ZEND_FUNCTION(OPcache_persistent_clear) if (!zend_opcache_static_cache_explicit_clear_prevalidated()) { zend_opcache_static_cache_restore_context(previous_context); - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the persistent cache"); + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the pinned cache"); RETURN_THROWS(); } - zend_opcache_static_cache_mark_publish_skipped(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_mark_publish_skipped(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_restore_context(previous_context); } -ZEND_FUNCTION(OPcache_persistent_atomic_increment) +ZEND_FUNCTION(OPcache_pinned_atomic_increment) { zend_opcache_static_cache_context *previous_context; zend_long step = 1, new_value; @@ -1968,7 +1968,7 @@ ZEND_FUNCTION(OPcache_persistent_atomic_increment) RETURN_THROWS(); } - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock)) { zend_opcache_static_cache_restore_context(previous_context); @@ -2000,7 +2000,7 @@ ZEND_FUNCTION(OPcache_persistent_atomic_increment) zend_opcache_static_cache_restore_context(previous_context); } -ZEND_FUNCTION(OPcache_persistent_atomic_decrement) +ZEND_FUNCTION(OPcache_pinned_atomic_decrement) { zend_opcache_static_cache_context *previous_context; zend_string *key; @@ -2017,7 +2017,7 @@ ZEND_FUNCTION(OPcache_persistent_atomic_decrement) RETURN_THROWS(); } - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock)) { zend_opcache_static_cache_restore_context(previous_context); @@ -2049,7 +2049,7 @@ ZEND_FUNCTION(OPcache_persistent_atomic_decrement) zend_opcache_static_cache_restore_context(previous_context); } -ZEND_FUNCTION(OPcache_persistent_cache_info) +ZEND_FUNCTION(OPcache_pinned_cache_info) { zend_opcache_static_cache_context *previous_context; @@ -2061,7 +2061,7 @@ ZEND_FUNCTION(OPcache_persistent_cache_info) RETURN_THROWS(); } - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_persistent_context_state); + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_populate_info(return_value); zend_opcache_static_cache_restore_context(previous_context); } diff --git a/ext/opcache/zend_static_cache.h b/ext/opcache/zend_static_cache.h index b5ce9458e0bc..4ef30c9ab40e 100644 --- a/ext/opcache/zend_static_cache.h +++ b/ext/opcache/zend_static_cache.h @@ -79,13 +79,13 @@ void zend_opcache_static_cache_mshutdown(void); zend_result zend_opcache_static_cache_rshutdown(void); void zend_opcache_static_cache_invalidate_all(void); void zend_opcache_static_cache_volatile_get_status(zval *return_value); -void zend_opcache_static_cache_persistent_get_status(zval *return_value); +void zend_opcache_static_cache_pinned_get_status(zval *return_value); bool zend_opcache_static_cache_volatile_is_enabled(void); bool zend_opcache_static_cache_volatile_is_available(void); const char *zend_opcache_static_cache_volatile_failure_reason(void); -bool zend_opcache_static_cache_persistent_is_enabled(void); -bool zend_opcache_static_cache_persistent_is_available(void); -const char *zend_opcache_static_cache_persistent_failure_reason(void); +bool zend_opcache_static_cache_pinned_is_enabled(void); +bool zend_opcache_static_cache_pinned_is_available(void); +const char *zend_opcache_static_cache_pinned_failure_reason(void); END_EXTERN_C() diff --git a/ext/opcache/zend_static_cache_entries.c b/ext/opcache/zend_static_cache_entries.c index e63ea3fe0025..5def02a6e105 100644 --- a/ext/opcache/zend_static_cache_entries.c +++ b/ext/opcache/zend_static_cache_entries.c @@ -2035,5 +2035,5 @@ bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long void zend_opcache_static_cache_release_request_local_slots(void) { zend_opcache_static_cache_release_request_local_slot_context(&zend_opcache_static_cache_volatile_request_local_slots); - zend_opcache_static_cache_release_request_local_slot_context(&zend_opcache_static_cache_persistent_request_local_slots); + zend_opcache_static_cache_release_request_local_slot_context(&zend_opcache_static_cache_pinned_request_local_slots); } diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index 6ed4945dd23f..0646ed8b284a 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -93,7 +93,7 @@ #define ZEND_OPCACHE_STATIC_CACHE_ENTRY_RESERVED_COMBINED_VALUE_KEY 0x0001U -#define ZEND_OPCACHE_STATIC_CACHE_PERSISTENT_ATTRIBUTE "opcache\\persistentstatic" +#define ZEND_OPCACHE_STATIC_CACHE_PINNED_ATTRIBUTE "opcache\\pinnedstatic" #define ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_ATTRIBUTE "opcache\\volatilestatic" #define ZEND_OPCACHE_STATIC_CACHE_STRATEGY_IMMEDIATE 0 @@ -103,12 +103,12 @@ #ifndef ZEND_WIN32 # define ZEND_OPCACHE_STATIC_CACHE_VOLATILE_SEM_FILENAME_PREFIX ".ZendVolatileCacheSem." -# define ZEND_OPCACHE_STATIC_CACHE_PERSISTENT_SEM_FILENAME_PREFIX ".ZendPersistentCacheSem." +# define ZEND_OPCACHE_STATIC_CACHE_PINNED_SEM_FILENAME_PREFIX ".ZendPinnedCacheSem." #endif typedef enum _zend_opcache_static_cache_kind { ZEND_OPCACHE_STATIC_CACHE_NONE, - ZEND_OPCACHE_STATIC_CACHE_PERSISTENT, + ZEND_OPCACHE_STATIC_CACHE_PINNED, ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC, ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING, ZEND_OPCACHE_STATIC_CACHE_CONFLICT @@ -163,7 +163,7 @@ typedef struct _zend_opcache_static_cache_entry_lock_lease { } zend_opcache_static_cache_entry_lock_lease; /* The same storage primitives serve the explicit volatile cache, VolatileStatic, and - * PersistentStatic. Callers switch this TLS context around short critical sections + * PinnedStatic. Callers switch this TLS context around short critical sections * so allocator, lookup-cache, and lock helpers always operate on the right SHM * segment without threading a context argument through every hot helper. */ typedef struct _zend_opcache_static_cache_header { @@ -364,11 +364,11 @@ extern zend_class_entry *zend_opcache_static_cache_exception_ce; extern zend_class_entry *zend_opcache_static_cache_strategy_ce; extern zend_class_entry *zend_opcache_static_cache_info_ce; extern zend_opcache_static_cache_context zend_opcache_static_cache_volatile_context_state; -extern zend_opcache_static_cache_context zend_opcache_static_cache_persistent_context_state; +extern zend_opcache_static_cache_context zend_opcache_static_cache_pinned_context_state; extern bool zend_opcache_static_cache_subsystem_disabled; extern const char *zend_opcache_static_cache_subsystem_failure_reason; extern ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_volatile_runtime_state; -extern ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_persistent_runtime_state; +extern ZEND_EXT_TLS zend_opcache_static_cache_runtime zend_opcache_static_cache_pinned_runtime_state; extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_attribute_classes; extern ZEND_EXT_TLS bool zend_opcache_static_cache_attribute_classes_initialized; extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_ignored_classes; @@ -389,7 +389,7 @@ extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_tracked_objects; extern ZEND_EXT_TLS bool zend_opcache_static_cache_has_tracked_arrays; extern ZEND_EXT_TLS bool zend_opcache_static_cache_has_tracked_objects; extern ZEND_EXT_TLS bool zend_opcache_static_cache_volatile_skip_publish; -extern ZEND_EXT_TLS bool zend_opcache_static_cache_persistent_skip_publish; +extern ZEND_EXT_TLS bool zend_opcache_static_cache_pinned_skip_publish; extern ZEND_EXT_TLS zend_opcache_static_cache_shared_graph_ref *zend_opcache_static_cache_shared_graph_refs; extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_shared_graph_ref_count; extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_shared_graph_ref_capacity; @@ -407,13 +407,13 @@ extern ZEND_EXT_TLS zend_opcache_static_cache_static_slot_handle *zend_opcache_s extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_capture_arrays; extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_capture_objects; extern ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_volatile_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; -extern ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_persistent_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; +extern ZEND_EXT_TLS zend_opcache_static_cache_lookup_entry zend_opcache_static_cache_pinned_lookup_entry_storage[ZEND_OPCACHE_STATIC_CACHE_LOOKUP_BUCKETS]; extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_volatile_request_local_slots; -extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_request_local_slots; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_pinned_request_local_slots; extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_volatile_entry_locks; -extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_persistent_entry_locks; +extern ZEND_EXT_TLS HashTable *zend_opcache_static_cache_pinned_entry_locks; extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_volatile_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; -extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_persistent_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; +extern ZEND_EXT_TLS uint32_t zend_opcache_static_cache_pinned_entry_lock_counts[ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES]; void zend_opcache_static_cache_reset_runtime(void); void zend_opcache_static_cache_reset_storage(void); @@ -832,8 +832,8 @@ static zend_always_inline void zend_opcache_static_cache_restore_context(zend_op static zend_always_inline zend_opcache_static_cache_runtime *zend_opcache_static_cache_context_runtime(zend_opcache_static_cache_context *context) { /* Runtime readiness is request-local under ZTS, while storage remains shared. */ - return context == &zend_opcache_static_cache_persistent_context_state - ? &zend_opcache_static_cache_persistent_runtime_state + return context == &zend_opcache_static_cache_pinned_context_state + ? &zend_opcache_static_cache_pinned_runtime_state : &zend_opcache_static_cache_volatile_runtime_state ; } @@ -845,16 +845,16 @@ static zend_always_inline zend_opcache_static_cache_runtime *zend_opcache_static static zend_always_inline zend_opcache_static_cache_lookup_entry *zend_opcache_static_cache_active_lookup_entries(void) { - return zend_opcache_static_cache_active_context() == &zend_opcache_static_cache_persistent_context_state - ? zend_opcache_static_cache_persistent_lookup_entry_storage + return zend_opcache_static_cache_active_context() == &zend_opcache_static_cache_pinned_context_state + ? zend_opcache_static_cache_pinned_lookup_entry_storage : zend_opcache_static_cache_volatile_lookup_entry_storage ; } static zend_always_inline HashTable **zend_opcache_static_cache_active_request_local_slots_ptr(void) { - return zend_opcache_static_cache_active_context() == &zend_opcache_static_cache_persistent_context_state - ? &zend_opcache_static_cache_persistent_request_local_slots + return zend_opcache_static_cache_active_context() == &zend_opcache_static_cache_pinned_context_state + ? &zend_opcache_static_cache_pinned_request_local_slots : &zend_opcache_static_cache_volatile_request_local_slots ; } @@ -1041,8 +1041,8 @@ static zend_always_inline void zend_opcache_static_cache_block_mark_used(zend_op static zend_always_inline void zend_opcache_static_cache_clear_lookup_cache_context(zend_opcache_static_cache_context *context) { - zend_opcache_static_cache_lookup_entry *entries = context == &zend_opcache_static_cache_persistent_context_state - ? zend_opcache_static_cache_persistent_lookup_entry_storage + zend_opcache_static_cache_lookup_entry *entries = context == &zend_opcache_static_cache_pinned_context_state + ? zend_opcache_static_cache_pinned_lookup_entry_storage : zend_opcache_static_cache_volatile_lookup_entry_storage ; @@ -1061,13 +1061,13 @@ static zend_always_inline void zend_opcache_static_cache_lookup_cache_reset_entr static zend_always_inline void zend_opcache_static_cache_clear_lookup_caches(void) { zend_opcache_static_cache_clear_lookup_cache_context(&zend_opcache_static_cache_volatile_context_state); - zend_opcache_static_cache_clear_lookup_cache_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_clear_lookup_cache_context(&zend_opcache_static_cache_pinned_context_state); } static zend_always_inline bool *zend_opcache_static_cache_skip_publish_ptr(zend_opcache_static_cache_context *context) { - return context == &zend_opcache_static_cache_persistent_context_state - ? &zend_opcache_static_cache_persistent_skip_publish + return context == &zend_opcache_static_cache_pinned_context_state + ? &zend_opcache_static_cache_pinned_skip_publish : &zend_opcache_static_cache_volatile_skip_publish ; } @@ -1085,7 +1085,7 @@ static zend_always_inline bool zend_opcache_static_cache_publish_skipped(zend_op static zend_always_inline void zend_opcache_static_cache_reset_publish_skips(void) { zend_opcache_static_cache_volatile_skip_publish = false; - zend_opcache_static_cache_persistent_skip_publish = false; + zend_opcache_static_cache_pinned_skip_publish = false; } #endif /* ZEND_STATIC_CACHE_INTERNAL_H */ diff --git a/ext/opcache/zend_static_cache_statics.c b/ext/opcache/zend_static_cache_statics.c index fbb09b294907..db2ee77544d8 100644 --- a/ext/opcache/zend_static_cache_statics.c +++ b/ext/opcache/zend_static_cache_statics.c @@ -134,7 +134,7 @@ static zend_opcache_static_cache_kind zend_opcache_static_cache_kind_from_attrib zend_class_entry *scope, zend_long *ttl) { - bool persistent_static; + bool pinned_static; zend_attribute *volatile_static; if (ttl != NULL) { @@ -145,14 +145,14 @@ static zend_opcache_static_cache_kind zend_opcache_static_cache_kind_from_attrib return ZEND_OPCACHE_STATIC_CACHE_NONE; } - persistent_static = zend_get_attribute_str(attributes, ZEND_STRL(ZEND_OPCACHE_STATIC_CACHE_PERSISTENT_ATTRIBUTE)) != NULL; + pinned_static = zend_get_attribute_str(attributes, ZEND_STRL(ZEND_OPCACHE_STATIC_CACHE_PINNED_ATTRIBUTE)) != NULL; volatile_static = zend_get_attribute_str(attributes, ZEND_STRL(ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_ATTRIBUTE)); - if (persistent_static && volatile_static != NULL) { + if (pinned_static && volatile_static != NULL) { return ZEND_OPCACHE_STATIC_CACHE_CONFLICT; } - if (persistent_static) { - return ZEND_OPCACHE_STATIC_CACHE_PERSISTENT; + if (pinned_static) { + return ZEND_OPCACHE_STATIC_CACHE_PINNED; } if (volatile_static != NULL) { @@ -165,7 +165,7 @@ static zend_opcache_static_cache_kind zend_opcache_static_cache_kind_from_attrib static bool zend_opcache_static_cache_handle_attribute_conflict(zend_opcache_static_cache_kind kind) { if (kind == ZEND_OPCACHE_STATIC_CACHE_CONFLICT) { - zend_error(E_ERROR, "OPcache\\PersistentStatic and OPcache\\VolatileStatic cannot be combined on the same target"); + zend_error(E_ERROR, "OPcache\\PinnedStatic and OPcache\\VolatileStatic cannot be combined on the same target"); return false; } @@ -206,19 +206,19 @@ static zend_opcache_static_cache_context *zend_opcache_static_cache_context_for_ { return kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC || kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING ? &zend_opcache_static_cache_volatile_context_state - : &zend_opcache_static_cache_persistent_context_state + : &zend_opcache_static_cache_pinned_context_state ; } static const char *zend_opcache_static_cache_key_prefix_for_kind(zend_opcache_static_cache_kind kind) { - return kind == ZEND_OPCACHE_STATIC_CACHE_PERSISTENT ? "persistent_static" : "volatile_static"; + return kind == ZEND_OPCACHE_STATIC_CACHE_PINNED ? "pinned_static" : "volatile_static"; } static bool zend_opcache_static_cache_kind_tracks_reachable_arrays( zend_opcache_static_cache_kind kind) { - return kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING || kind == ZEND_OPCACHE_STATIC_CACHE_PERSISTENT; + return kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC_TRACKING || kind == ZEND_OPCACHE_STATIC_CACHE_PINNED; } static bool zend_opcache_static_cache_kind_tracks_reachable_objects( @@ -238,7 +238,7 @@ static bool zend_opcache_static_cache_kind_tracks_reachable_mutations( static bool zend_opcache_static_cache_kind_publishes_immediately( zend_opcache_static_cache_kind kind) { - return kind == ZEND_OPCACHE_STATIC_CACHE_PERSISTENT || kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC; + return kind == ZEND_OPCACHE_STATIC_CACHE_PINNED || kind == ZEND_OPCACHE_STATIC_CACHE_VOLATILE_STATIC; } static bool zend_opcache_static_cache_kind_defers_reachable_mutation_publish( @@ -2134,7 +2134,7 @@ static void zend_opcache_static_cache_publish_class_blob_fast(zend_opcache_stati previous_context = zend_opcache_static_cache_activate_context(class_blob_handle->context); if (zend_opcache_static_cache_wlock()) { if (zend_opcache_static_cache_header_init_locked()) { - /* Immediate class blobs publish root assignments immediately. PersistentStatic + /* Immediate class blobs publish root assignments immediately. PinnedStatic * array graphs are re-registered after publication so later array writes * also fail or succeed at the write site. */ class_blob_handle->dirty = true; @@ -2737,7 +2737,7 @@ static bool zend_opcache_static_cache_class_access_filter( return false; } -static void zend_opcache_static_cache_publish_persistent_static_properties_fast(zend_class_entry *ce) +static void zend_opcache_static_cache_publish_pinned_static_properties_fast(zend_class_entry *ce) { zend_opcache_static_cache_context *context, *previous_context; zend_opcache_static_cache_static_slot_handle *handle; @@ -3059,7 +3059,7 @@ static void zend_opcache_static_cache_capture_decoded_reachable_value_ex( } if (zend_opcache_static_cache_capture_handle != NULL && - zend_opcache_static_cache_capture_handle->kind == ZEND_OPCACHE_STATIC_CACHE_PERSISTENT + zend_opcache_static_cache_capture_handle->kind == ZEND_OPCACHE_STATIC_CACHE_PINNED ) { return; } @@ -3391,7 +3391,7 @@ static void zend_opcache_static_cache_class_update(zend_class_entry *ce) class_blob_handle = zend_opcache_static_cache_find_class_blob_handle(ce); if (class_blob_handle == NULL) { - zend_opcache_static_cache_publish_persistent_static_properties_fast(ce); + zend_opcache_static_cache_publish_pinned_static_properties_fast(ce); return; } @@ -3820,7 +3820,7 @@ void zend_opcache_static_cache_request_shutdown(void) { if (zend_opcache_static_cache_attribute_classes_initialized || zend_opcache_static_cache_function_statics_initialized || zend_opcache_static_cache_class_blob_handles_initialized) { zend_opcache_static_cache_flush_pending(); - zend_opcache_static_cache_publish_context(&zend_opcache_static_cache_persistent_context_state); + zend_opcache_static_cache_publish_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_publish_context(&zend_opcache_static_cache_volatile_context_state); if (zend_opcache_static_cache_attribute_classes_initialized) { diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index bc8c507eceae..dbd061028637 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -871,16 +871,16 @@ static void zend_opcache_static_cache_unlock_impl(void) static HashTable **zend_opcache_static_cache_entry_locks_ptr_for_context(zend_opcache_static_cache_context *context) { - return context == &zend_opcache_static_cache_persistent_context_state - ? &zend_opcache_static_cache_persistent_entry_locks + return context == &zend_opcache_static_cache_pinned_context_state + ? &zend_opcache_static_cache_pinned_entry_locks : &zend_opcache_static_cache_volatile_entry_locks ; } static uint32_t *zend_opcache_static_cache_entry_lock_counts_for_context(zend_opcache_static_cache_context *context) { - return context == &zend_opcache_static_cache_persistent_context_state - ? zend_opcache_static_cache_persistent_entry_lock_counts + return context == &zend_opcache_static_cache_pinned_context_state + ? zend_opcache_static_cache_pinned_entry_lock_counts : zend_opcache_static_cache_volatile_entry_lock_counts ; } @@ -1152,13 +1152,13 @@ static void zend_opcache_static_cache_ensure_entry_lock_process(void) zend_opcache_static_cache_volatile_entry_lock_counts, ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP); zend_opcache_static_cache_release_entry_lock_context( - &zend_opcache_static_cache_persistent_context_state, - &zend_opcache_static_cache_persistent_entry_locks, - zend_opcache_static_cache_persistent_entry_lock_counts, + &zend_opcache_static_cache_pinned_context_state, + &zend_opcache_static_cache_pinned_entry_locks, + zend_opcache_static_cache_pinned_entry_lock_counts, ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP); #ifdef ZTS zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_volatile_context_state.storage); - zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_persistent_context_state.storage); + zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_pinned_context_state.storage); zend_opcache_static_cache_entry_locks_process_is_fork_child = true; #endif zend_opcache_static_cache_entry_lock_owner_pid = current_pid; @@ -1759,8 +1759,8 @@ void zend_opcache_static_cache_reset_runtime(void) memset(runtime, 0, sizeof(*runtime)); - runtime->configured_memory = context == &zend_opcache_static_cache_persistent_context_state - ? ZCG(accel_directives).static_cache_persistent_size_mb + runtime->configured_memory = context == &zend_opcache_static_cache_pinned_context_state + ? ZCG(accel_directives).static_cache_pinned_size_mb : ZCG(accel_directives).static_cache_volatile_size_mb ; runtime->enabled = runtime->configured_memory != 0; @@ -2355,15 +2355,15 @@ void zend_opcache_static_cache_release_request_entry_locks(void) ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_PRESERVE_LEASES ); zend_opcache_static_cache_release_entry_lock_context( - &zend_opcache_static_cache_persistent_context_state, - &zend_opcache_static_cache_persistent_entry_locks, - zend_opcache_static_cache_persistent_entry_lock_counts, + &zend_opcache_static_cache_pinned_context_state, + &zend_opcache_static_cache_pinned_entry_locks, + zend_opcache_static_cache_pinned_entry_lock_counts, ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_PRESERVE_LEASES ); #if !defined(ZEND_WIN32) && defined(ZTS) if (zend_opcache_static_cache_entry_locks_process_is_fork_child) { zend_opcache_static_cache_entry_locks_shutdown(&zend_opcache_static_cache_volatile_context_state.storage); - zend_opcache_static_cache_entry_locks_shutdown(&zend_opcache_static_cache_persistent_context_state.storage); + zend_opcache_static_cache_entry_locks_shutdown(&zend_opcache_static_cache_pinned_context_state.storage); } #endif #ifndef ZEND_WIN32 From b1fe59cf3949dabfb5a1f29fce7a9c1c5f92cb21 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 04:25:46 +0000 Subject: [PATCH 16/29] trying rshutdown graph compaction --- ...cit_cache_shared_graph_relocation_001.phpt | 56 +++ ext/opcache/zend_static_cache.c | 8 +- ext/opcache/zend_static_cache_internal.h | 6 +- ext/opcache/zend_static_cache_shared_graph.c | 343 +++++++++++++++++- ext/opcache/zend_static_cache_storage.c | 89 ++++- 5 files changed, 473 insertions(+), 29 deletions(-) create mode 100644 ext/opcache/tests/static_cache_explicit_cache_shared_graph_relocation_001.phpt diff --git a/ext/opcache/tests/static_cache_explicit_cache_shared_graph_relocation_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_shared_graph_relocation_001.phpt new file mode 100644 index 000000000000..86265b57cd27 --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_shared_graph_relocation_001.phpt @@ -0,0 +1,56 @@ +--TEST-- +OPcache explicit volatile cache relocates unreferenced shared graph blocks during compaction +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +--FILE-- + $rows, + 'meta' => ['kind' => 'relocatable'], + ]; +} + +OPcache\volatile_clear(); + +var_dump(OPcache\volatile_store('shared_graph_relocation_first', str_repeat('A', 1500000))); +var_dump(OPcache\volatile_store('shared_graph_relocation_graph', build_relocatable_graph())); +var_dump(OPcache\volatile_store('shared_graph_relocation_third', str_repeat('C', 1500000))); + +OPcache\volatile_delete('shared_graph_relocation_first'); + +var_dump(OPcache\volatile_store('shared_graph_relocation_merged', str_repeat('M', 3200000))); + +$graph = OPcache\volatile_fetch('shared_graph_relocation_graph'); +var_dump($graph['rows'][123][1]); +var_dump($graph['rows'][123][2][1]); +var_dump($graph['meta']['kind']); +var_dump(strlen(OPcache\volatile_fetch('shared_graph_relocation_merged'))); +var_dump(strlen(OPcache\volatile_fetch('shared_graph_relocation_third'))); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +string(48) "TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT" +int(369) +string(11) "relocatable" +int(3200000) +int(1500000) diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index d5533ece4fd0..2feab34d224f 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -1290,11 +1290,17 @@ static zend_result zend_opcache_static_cache_rinit(void) zend_result zend_opcache_static_cache_rshutdown(void) { + bool shared_graph_refs_released; + zend_opcache_static_cache_clear_lookup_caches(); zend_opcache_static_cache_request_shutdown(); zend_opcache_static_cache_release_request_entry_locks(); zend_opcache_static_cache_release_request_local_slots(); - zend_opcache_static_cache_release_request_shared_graph_refs(); + + shared_graph_refs_released = zend_opcache_static_cache_release_request_shared_graph_refs(); + if (shared_graph_refs_released) { + zend_opcache_static_cache_compact_after_request_shutdown(); + } EG(static_cache_class_access_active) = false; EG(tracked_mutation_hooks_active) = false; diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index 0646ed8b284a..692699fc2be5 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -421,6 +421,8 @@ bool zend_opcache_static_cache_header_init_locked(void); void zend_opcache_static_cache_free_locked(uint32_t payload_offset); uint32_t zend_opcache_static_cache_alloc_locked(size_t size, const void *source); bool zend_opcache_static_cache_compact_to_fit_locked(size_t size); +bool zend_opcache_static_cache_compact_available_locked(void); +void zend_opcache_static_cache_compact_after_request_shutdown(void); bool zend_opcache_static_cache_startup_storage_before_request(void); void zend_opcache_static_cache_shutdown_storage(void); void zend_opcache_static_cache_ensure_ready(void); @@ -461,13 +463,15 @@ bool zend_opcache_static_cache_shared_graph_copy_fits_buffer( size_t target_buffer_len); bool zend_opcache_static_cache_fetch_shared_graph(const unsigned char *buffer, size_t buffer_len, zval *destination); bool zend_opcache_static_cache_shared_graph_can_overwrite_payload_locked(uint32_t payload_offset); +bool zend_opcache_static_cache_shared_graph_can_move_payload_locked(uint32_t payload_offset); +bool zend_opcache_static_cache_shared_graph_rebase_moved_payload_locked(uint32_t payload_offset, ptrdiff_t delta); bool zend_opcache_static_cache_shared_graph_acquire_locked(uint32_t payload_offset); bool zend_opcache_static_cache_shared_graph_retire_payload_locked(uint32_t payload_offset); bool zend_opcache_static_cache_shared_graph_release_ref_locked(uint32_t payload_offset); bool zend_opcache_static_cache_has_request_shared_graph_ref(uint32_t payload_offset); void zend_opcache_static_cache_register_shared_graph_ref(uint32_t payload_offset); void zend_opcache_static_cache_defer_retired_shared_graph_free(uint32_t payload_offset); -void zend_opcache_static_cache_release_request_shared_graph_refs(void); +bool zend_opcache_static_cache_release_request_shared_graph_refs(void); bool zend_opcache_static_cache_clear_locked(void); bool zend_opcache_static_cache_prepare_value( zend_opcache_static_cache_prepared_value *prepared, diff --git a/ext/opcache/zend_static_cache_shared_graph.c b/ext/opcache/zend_static_cache_shared_graph.c index bd8d4f5911c5..fd5c92c67bdf 100644 --- a/ext/opcache/zend_static_cache_shared_graph.c +++ b/ext/opcache/zend_static_cache_shared_graph.c @@ -1150,14 +1150,15 @@ static zend_opcache_static_cache_shared_graph_header *zend_opcache_static_cache_ return header; } -static void zend_opcache_static_cache_free_retired_shared_graphs(void) +static bool zend_opcache_static_cache_free_retired_shared_graphs(void) { zend_opcache_static_cache_shared_graph_ref *ref; zend_opcache_static_cache_context *previous_context; uint32_t index; + bool freed = false; if (zend_opcache_static_cache_retired_shared_graph_count == 0) { - return; + return false; } for (index = 0; index < zend_opcache_static_cache_retired_shared_graph_count; index++) { @@ -1171,6 +1172,7 @@ static void zend_opcache_static_cache_free_retired_shared_graphs(void) if (zend_opcache_static_cache_wlock()) { if (zend_opcache_static_cache_header_is_initialized_locked()) { zend_opcache_static_cache_free_locked(ref->payload_offset); + freed = true; } zend_opcache_static_cache_unlock(); @@ -1184,6 +1186,8 @@ static void zend_opcache_static_cache_free_retired_shared_graphs(void) zend_opcache_static_cache_retired_shared_graphs = NULL; zend_opcache_static_cache_retired_shared_graph_count = 0; zend_opcache_static_cache_retired_shared_graph_capacity = 0; + + return freed; } bool zend_opcache_static_cache_calculate_shared_graph_size( @@ -1391,7 +1395,244 @@ bool zend_opcache_static_cache_fetch_shared_graph( return result; } +static zend_always_inline bool zend_opcache_static_cache_shared_graph_pointer_in_range( + const void *pointer, + const unsigned char *base, + size_t len) +{ + uintptr_t address, start; + + if (pointer == NULL || base == NULL || len == 0) { + return false; + } + + address = (uintptr_t) pointer; + start = (uintptr_t) base; + + return address >= start && address - start < len; +} + +static zend_always_inline void *zend_opcache_static_cache_shared_graph_rebase_pointer( + void *pointer, + const unsigned char *old_base, + size_t len, + ptrdiff_t delta) +{ + if (!zend_opcache_static_cache_shared_graph_pointer_in_range(pointer, old_base, len)) { + return pointer; + } + + return (void *) ((char *) pointer - delta); +} + +static bool zend_opcache_static_cache_shared_graph_rebase_direct_array( + zend_array *array, + const unsigned char *old_base, + const unsigned char *new_base, + size_t len, + ptrdiff_t delta, + HashTable *seen_arrays +); + +static bool zend_opcache_static_cache_shared_graph_rebase_direct_zval( + zval *value, + const unsigned char *old_base, + const unsigned char *new_base, + size_t len, + ptrdiff_t delta, + HashTable *seen_arrays) +{ + zend_array *array; + + switch (Z_TYPE_P(value)) { + case IS_STRING: + Z_STR_P(value) = (zend_string *) zend_opcache_static_cache_shared_graph_rebase_pointer( + Z_STR_P(value), + old_base, + len, + delta + ); + return true; + case IS_ARRAY: + array = (zend_array *) zend_opcache_static_cache_shared_graph_rebase_pointer( + Z_ARR_P(value), + old_base, + len, + delta + ); + Z_ARR_P(value) = array; + if (!zend_opcache_static_cache_shared_graph_pointer_in_range(array, new_base, len)) { + return true; + } + + return zend_opcache_static_cache_shared_graph_rebase_direct_array( + array, + old_base, + new_base, + len, + delta, + seen_arrays + ); + default: + return true; + } +} + +static bool zend_opcache_static_cache_shared_graph_rebase_direct_array( + zend_array *array, + const unsigned char *old_base, + const unsigned char *new_base, + size_t len, + ptrdiff_t delta, + HashTable *seen_arrays) +{ + zend_ulong key; + zval *packed; + Bucket *bucket; + uint32_t index; + void *data; + + if (!zend_opcache_static_cache_shared_graph_pointer_in_range(array, new_base, len)) { + return true; + } + + key = (zend_ulong) (uintptr_t) array; + if (zend_hash_index_exists(seen_arrays, key)) { + return true; + } + if (zend_hash_index_add_empty_element(seen_arrays, key) == NULL) { + return false; + } + + data = HT_GET_DATA_ADDR(array); + data = zend_opcache_static_cache_shared_graph_rebase_pointer(data, old_base, len, delta); + HT_SET_DATA_ADDR(array, data); + if (!zend_opcache_static_cache_shared_graph_pointer_in_range(data, new_base, len)) { + return false; + } + + if (HT_IS_PACKED(array)) { + packed = array->arPacked; + for (index = 0; index < array->nNumUsed; index++) { + if (!zend_opcache_static_cache_shared_graph_rebase_direct_zval( + &packed[index], + old_base, + new_base, + len, + delta, + seen_arrays) + ) { + return false; + } + } + } else { + bucket = array->arData; + for (index = 0; index < array->nNumUsed; index++) { + if (bucket[index].key != NULL) { + bucket[index].key = (zend_string *) zend_opcache_static_cache_shared_graph_rebase_pointer( + bucket[index].key, + old_base, + len, + delta + ); + + if (!zend_opcache_static_cache_shared_graph_pointer_in_range(bucket[index].key, new_base, len)) { + return false; + } + } + + if (!zend_opcache_static_cache_shared_graph_rebase_direct_zval( + &bucket[index].val, + old_base, + new_base, + len, + delta, + seen_arrays) + ) { + return false; + } + } + } + + return true; +} + +static bool zend_opcache_static_cache_shared_graph_rebase_graph_value( + const unsigned char *buffer, + const zend_opcache_static_cache_shared_graph_value *value, + const unsigned char *old_base, + const unsigned char *new_base, + size_t len, + ptrdiff_t delta, + HashTable *seen_arrays) +{ + const zend_opcache_static_cache_shared_graph_array *graph_array; + const zend_opcache_static_cache_shared_graph_array_element *graph_elements; + const zend_opcache_static_cache_shared_graph_object *graph_object; + const zend_opcache_static_cache_shared_graph_property *properties; + zend_array *array; + uint32_t index; + + switch (value->type) { + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_ARRAY: + if ((uint32_t) value->payload.offset == 0) { + return true; + } + + array = (zend_array *) (void *) (buffer + (uint32_t) value->payload.offset); + return zend_opcache_static_cache_shared_graph_rebase_direct_array( + array, + old_base, + new_base, + len, + delta, + seen_arrays + ); + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DYNAMIC_ARRAY: + graph_array = (const zend_opcache_static_cache_shared_graph_array *) (buffer + (uint32_t) value->payload.offset); + graph_elements = (const zend_opcache_static_cache_shared_graph_array_element *) (buffer + graph_array->elements_offset); + for (index = 0; index < graph_array->count; index++) { + if (!zend_opcache_static_cache_shared_graph_rebase_graph_value( + buffer, + &graph_elements[index].value, + old_base, + new_base, + len, + delta, + seen_arrays) + ) { + return false; + } + } + return true; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT: + graph_object = (const zend_opcache_static_cache_shared_graph_object *) (buffer + (uint32_t) value->payload.offset); + properties = (const zend_opcache_static_cache_shared_graph_property *) (buffer + graph_object->properties_offset); + for (index = 0; index < graph_object->property_count; index++) { + if (!zend_opcache_static_cache_shared_graph_rebase_graph_value( + buffer, + &properties[index].value, + old_base, + new_base, + len, + delta, + seen_arrays) + ) { + return false; + } + } + return true; + default: + return true; + } +} + bool zend_opcache_static_cache_shared_graph_can_overwrite_payload_locked(uint32_t payload_offset) +{ + return zend_opcache_static_cache_shared_graph_can_move_payload_locked(payload_offset); +} + +bool zend_opcache_static_cache_shared_graph_can_move_payload_locked(uint32_t payload_offset) { zend_opcache_static_cache_shared_graph_header *header = zend_opcache_static_cache_shared_graph_payload_header(payload_offset); @@ -1402,6 +1643,88 @@ bool zend_opcache_static_cache_shared_graph_can_overwrite_payload_locked(uint32_ return zend_atomic_int_load_ex(&header->ref_state) == 0; } +bool zend_opcache_static_cache_shared_graph_rebase_moved_payload_locked(uint32_t payload_offset, ptrdiff_t delta) +{ + const unsigned char *buffer, *old_buffer, *new_base, *old_base; + zend_opcache_static_cache_shared_graph_header *header; + zend_opcache_static_cache_shared_graph_value root_value; + HashTable seen_arrays; + uint32_t root_type; + size_t buffer_len, graph_len, old_padding, new_padding; + bool result; + + if (payload_offset == 0 || delta == 0) { + return true; + } + + buffer_len = zend_opcache_static_cache_block_payload_capacity(payload_offset); + if (buffer_len == 0) { + return false; + } + + buffer = (const unsigned char *) zend_opcache_static_cache_ptr(payload_offset); + old_buffer = buffer + delta; + old_padding = zend_opcache_static_cache_shared_graph_alignment_padding(old_buffer); + new_padding = zend_opcache_static_cache_shared_graph_alignment_padding(buffer); + if (old_padding != new_padding) { + return false; + } + + buffer = zend_opcache_static_cache_shared_graph_locate_buffer(buffer, buffer_len, &graph_len); + if (buffer == NULL) { + return false; + } + + header = (zend_opcache_static_cache_shared_graph_header *) buffer; + if (header->magic != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_MAGIC || + header->version != ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VERSION || + (header->root_offset != 0 && header->root_offset >= graph_len) + ) { + return false; + } + + new_base = buffer; + old_base = old_buffer + old_padding; + root_type = header->root_type != 0 ? header->root_type : ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT; + memset(&root_value, 0, sizeof(root_value)); + root_value.type = (uint8_t) root_type; + root_value.payload.offset = header->root_offset; + + zend_hash_init(&seen_arrays, 8, NULL, NULL, 0); + switch (root_type) { + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_ARRAY: + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DYNAMIC_ARRAY: + result = zend_opcache_static_cache_shared_graph_rebase_graph_value( + buffer, + &root_value, + old_base, + new_base, + graph_len, + delta, + &seen_arrays + ); + break; + case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT: + result = header->root_offset != 0 && + zend_opcache_static_cache_shared_graph_rebase_graph_value( + buffer, + &root_value, + old_base, + new_base, + graph_len, + delta, + &seen_arrays + ); + break; + default: + result = false; + break; + } + zend_hash_destroy(&seen_arrays); + + return result; +} + bool zend_opcache_static_cache_shared_graph_acquire_locked(uint32_t payload_offset) { zend_opcache_static_cache_shared_graph_header *header = zend_opcache_static_cache_shared_graph_payload_header(payload_offset); @@ -1540,16 +1863,15 @@ void zend_opcache_static_cache_defer_retired_shared_graph_free(uint32_t payload_ ref->payload_offset = payload_offset; } -void zend_opcache_static_cache_release_request_shared_graph_refs(void) +bool zend_opcache_static_cache_release_request_shared_graph_refs(void) { zend_opcache_static_cache_shared_graph_ref *ref; zend_opcache_static_cache_context *previous_context; uint32_t index; + bool released = false; if (zend_opcache_static_cache_shared_graph_ref_count == 0) { - zend_opcache_static_cache_free_retired_shared_graphs(); - - return; + return zend_opcache_static_cache_free_retired_shared_graphs(); } for (index = 0; index < zend_opcache_static_cache_shared_graph_ref_count; index++) { @@ -1562,9 +1884,12 @@ void zend_opcache_static_cache_release_request_shared_graph_refs(void) previous_context = zend_opcache_static_cache_activate_context(ref->context); if (zend_opcache_static_cache_wlock()) { if (zend_opcache_static_cache_header_is_initialized_locked() && - zend_opcache_static_cache_shared_graph_release_ref_locked(ref->payload_offset) + ref->payload_offset != 0 ) { - zend_opcache_static_cache_free_locked(ref->payload_offset); + released = true; + if (zend_opcache_static_cache_shared_graph_release_ref_locked(ref->payload_offset)) { + zend_opcache_static_cache_free_locked(ref->payload_offset); + } } zend_opcache_static_cache_unlock(); @@ -1579,5 +1904,5 @@ void zend_opcache_static_cache_release_request_shared_graph_refs(void) zend_opcache_static_cache_shared_graph_ref_count = 0; zend_opcache_static_cache_shared_graph_ref_capacity = 0; - zend_opcache_static_cache_free_retired_shared_graphs(); + return zend_opcache_static_cache_free_retired_shared_graphs() || released; } diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index dbd061028637..5ac60e52cc6c 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -1645,8 +1645,9 @@ static bool zend_opcache_static_cache_block_is_movable_locked( ) { referenced = true; if (entry->value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { - /* Shared graphs may contain final-buffer pointers, so keep their block anchored. */ - return false; + if (!zend_opcache_static_cache_shared_graph_can_move_payload_locked(entry->value_offset)) { + return false; + } } } } @@ -1662,7 +1663,7 @@ static void zend_opcache_static_cache_update_moved_block_entries_locked( ) { zend_opcache_static_cache_entry *entries, *entry; - uint32_t index, delta; + uint32_t index, delta, new_value_offset; ZEND_ASSERT(new_block_offset <= old_block_offset); delta = old_block_offset - new_block_offset; @@ -1682,7 +1683,15 @@ static void zend_opcache_static_cache_update_moved_block_entries_locked( if (entry->value_offset != 0 && zend_opcache_static_cache_offset_in_block(entry->value_offset, old_block_offset, block_size) ) { - entry->value_offset -= delta; + new_value_offset = entry->value_offset - delta; + + if (entry->value_type == ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { + if (!zend_opcache_static_cache_shared_graph_rebase_moved_payload_locked(new_value_offset, delta)) { + ZEND_ASSERT(0); + } + } + + entry->value_offset = new_value_offset; } } } @@ -1952,25 +1961,13 @@ uint32_t zend_opcache_static_cache_alloc_locked(size_t size, const void *source) return block_offset + (uint32_t) sizeof(zend_opcache_static_cache_block); } -bool zend_opcache_static_cache_compact_to_fit_locked(size_t size) +static bool zend_opcache_static_cache_compact_movable_blocks_locked(zend_opcache_static_cache_header *header) { - zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); zend_opcache_static_cache_block *block, *free_block; - uint32_t required_block_size, used_end, offset, next_offset, block_size, write_offset, + uint32_t used_end, offset, next_offset, block_size, write_offset, previous_block_size = 0, last_block_offset = 0, free_size; bool moved = false, movable; - if (!header || !zend_opcache_static_cache_header_init_locked()) { - return false; - } - - if (!zend_opcache_static_cache_payload_size_to_block_size(size, &required_block_size) || - required_block_size > header->data_size || - !zend_opcache_static_cache_compaction_can_fit_locked(header, required_block_size) - ) { - return false; - } - used_end = header->data_offset + header->next_free; offset = header->data_offset; write_offset = header->data_offset; @@ -2045,6 +2042,62 @@ bool zend_opcache_static_cache_compact_to_fit_locked(size_t size) return moved; } +bool zend_opcache_static_cache_compact_to_fit_locked(size_t size) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + uint32_t required_block_size; + + if (!header || !zend_opcache_static_cache_header_init_locked()) { + return false; + } + + if (!zend_opcache_static_cache_payload_size_to_block_size(size, &required_block_size) || + required_block_size > header->data_size || + !zend_opcache_static_cache_compaction_can_fit_locked(header, required_block_size) + ) { + return false; + } + + return zend_opcache_static_cache_compact_movable_blocks_locked(header); +} + +bool zend_opcache_static_cache_compact_available_locked(void) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + + if (!header || + !zend_opcache_static_cache_header_init_locked() || + header->free_list == 0 || + !zend_opcache_static_cache_compaction_can_fit_locked(header, 0) + ) { + return false; + } + + return zend_opcache_static_cache_compact_movable_blocks_locked(header); +} + +static void zend_opcache_static_cache_compact_context_after_request_shutdown(zend_opcache_static_cache_context *context) +{ + zend_opcache_static_cache_context *previous_context; + + if (!zend_opcache_static_cache_context_runtime(context)->available) { + return; + } + + previous_context = zend_opcache_static_cache_activate_context(context); + if (zend_opcache_static_cache_wlock()) { + zend_opcache_static_cache_compact_available_locked(); + zend_opcache_static_cache_unlock(); + } + zend_opcache_static_cache_restore_context(previous_context); +} + +void zend_opcache_static_cache_compact_after_request_shutdown(void) +{ + zend_opcache_static_cache_compact_context_after_request_shutdown(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_compact_context_after_request_shutdown(&zend_opcache_static_cache_pinned_context_state); +} + bool zend_opcache_static_cache_startup_storage_before_request(void) { zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; From 926d8232bf7c389dae8e43af67f2e8fab303071e Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 05:24:22 +0000 Subject: [PATCH 17/29] fix realloc --- ...atile_cache_low_memory_compaction_001.phpt | 59 +++++ ...ed_shared_graph_anchor_compaction_001.phpt | 55 ++++ ...uest_shutdown_shared_graph_anchor_001.phpt | 48 ++++ ext/opcache/zend_static_cache.c | 56 ++--- ext/opcache/zend_static_cache_internal.h | 3 +- ext/opcache/zend_static_cache_storage.c | 234 +++++++++--------- 6 files changed, 300 insertions(+), 155 deletions(-) create mode 100644 ext/opcache/tests/static_cache_volatile_cache_low_memory_compaction_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_referenced_shared_graph_anchor_compaction_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_request_shutdown_shared_graph_anchor_001.phpt diff --git a/ext/opcache/tests/static_cache_volatile_cache_low_memory_compaction_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_low_memory_compaction_001.phpt new file mode 100644 index 000000000000..cc77e801f23f --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_low_memory_compaction_001.phpt @@ -0,0 +1,59 @@ +--TEST-- +OPcache volatile cache compacts before remaining tail memory drops below 3 MiB +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +--FILE-- + $rows, + 'meta' => ['kind' => 'low-memory'], + ]; +} + +OPcache\volatile_clear(); + +var_dump(OPcache\volatile_store('low_memory_first', str_repeat('A', 1500000))); +var_dump(OPcache\volatile_store('low_memory_graph', build_low_memory_graph())); +var_dump(OPcache\volatile_store('low_memory_third', str_repeat('C', 1500000))); + +OPcache\volatile_delete('low_memory_first'); + +var_dump(OPcache\volatile_store('low_memory_trigger', str_repeat('T', 1700000))); +$graph = OPcache\volatile_fetch('low_memory_graph'); +OPcache\volatile_delete('low_memory_trigger'); + +var_dump(OPcache\volatile_store('low_memory_merged', str_repeat('M', 3200000))); +var_dump($graph['rows'][123][1]); +var_dump($graph['rows'][123][2][1]); +var_dump($graph['meta']['kind']); +var_dump(strlen(OPcache\volatile_fetch('low_memory_merged'))); +var_dump(strlen(OPcache\volatile_fetch('low_memory_third'))); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +string(48) "TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT" +int(369) +string(10) "low-memory" +int(3200000) +int(1500000) diff --git a/ext/opcache/tests/static_cache_volatile_cache_referenced_shared_graph_anchor_compaction_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_referenced_shared_graph_anchor_compaction_001.phpt new file mode 100644 index 000000000000..996121ce3914 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_referenced_shared_graph_anchor_compaction_001.phpt @@ -0,0 +1,55 @@ +--TEST-- +OPcache volatile cache keeps referenced shared graph blocks anchored during compaction +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +--FILE-- + $rows, + 'meta' => ['kind' => 'anchored'], + ]; +} + +OPcache\volatile_clear(); + +var_dump(OPcache\volatile_store('shared_graph_anchor_first', str_repeat('A', 1500000))); +var_dump(OPcache\volatile_store('shared_graph_anchor_graph', build_anchored_graph())); +var_dump(OPcache\volatile_store('shared_graph_anchor_third', str_repeat('C', 1500000))); + +$graph = OPcache\volatile_fetch('shared_graph_anchor_graph'); +OPcache\volatile_delete('shared_graph_anchor_first'); + +var_dump(OPcache\volatile_store('shared_graph_anchor_merged', str_repeat('M', 3200000))); +var_dump($graph['rows'][123][1]); +var_dump($graph['rows'][123][2][1]); +var_dump($graph['meta']['kind']); +var_dump(OPcache\volatile_fetch('shared_graph_anchor_merged', 'MISS')); +var_dump(strlen(OPcache\volatile_fetch('shared_graph_anchor_third'))); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(false) +string(48) "TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT" +int(369) +string(8) "anchored" +string(4) "MISS" +int(1500000) diff --git a/ext/opcache/tests/static_cache_volatile_cache_request_shutdown_shared_graph_anchor_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_request_shutdown_shared_graph_anchor_001.phpt new file mode 100644 index 000000000000..b8d307d63313 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_request_shutdown_shared_graph_anchor_001.phpt @@ -0,0 +1,48 @@ +--TEST-- +OPcache volatile cache keeps request-held shared graphs anchored during shutdown +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $i, + 'text' => str_repeat($prefix, 32), + 'nested' => ['value' => $i * 3], + ]; + } + + return [ + 'name' => 'shutdown-anchor', + 'rows' => $rows, + ]; +} + +OPcache\volatile_clear(); + +var_dump(OPcache\volatile_store('fragment-padding', str_repeat('P', 131072))); +var_dump(OPcache\volatile_store('shutdown-anchor', build_shutdown_payload('A'))); +OPcache\volatile_delete('fragment-padding'); + +$_POST['shutdown-anchor'] = OPcache\volatile_fetch('shutdown-anchor'); + +echo $_POST['shutdown-anchor']['name'], "\n"; +echo $_POST['shutdown-anchor']['rows'][123]['text'], "\n"; +echo $_POST['shutdown-anchor']['rows'][123]['nested']['value'], "\n"; + +?> +--EXPECT-- +bool(true) +bool(true) +shutdown-anchor +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +369 diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index 2feab34d224f..17d2dcf57579 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -277,8 +277,6 @@ static zend_always_inline bool zend_opcache_static_cache_validate_api_value(zval return true; } -static void zend_opcache_static_cache_register_accelerator_handlers(void); - static void zend_opcache_static_cache_safe_direct_handlers_dtor(zval *zv) { pefree(Z_PTR_P(zv), true); @@ -1121,29 +1119,6 @@ zend_result zend_opcache_register_functions(int module_type) return zend_register_functions(NULL, ext_functions, NULL, module_type); } -zend_result zend_opcache_static_cache_minit(void) -{ - zend_opcache_static_cache_context *previous_context; - - zend_opcache_static_cache_subsystem_disabled = false; - zend_opcache_static_cache_subsystem_failure_reason = NULL; - zend_opcache_static_cache_safe_direct_classes_marked = false; - - zend_opcache_static_cache_register_classes(); - zend_opcache_static_cache_safe_direct_handlers_init(); - zend_opcache_static_cache_register_accelerator_handlers(); - - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); - zend_opcache_static_cache_reset_storage(); - - zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - zend_opcache_static_cache_reset_storage(); - - zend_opcache_static_cache_restore_context(previous_context); - - return SUCCESS; -} - static void zend_opcache_static_cache_startup(void) { const char *failure_reason; @@ -1290,17 +1265,11 @@ static zend_result zend_opcache_static_cache_rinit(void) zend_result zend_opcache_static_cache_rshutdown(void) { - bool shared_graph_refs_released; - zend_opcache_static_cache_clear_lookup_caches(); zend_opcache_static_cache_request_shutdown(); zend_opcache_static_cache_release_request_entry_locks(); zend_opcache_static_cache_release_request_local_slots(); - - shared_graph_refs_released = zend_opcache_static_cache_release_request_shared_graph_refs(); - if (shared_graph_refs_released) { - zend_opcache_static_cache_compact_after_request_shutdown(); - } + zend_opcache_static_cache_release_request_shared_graph_refs(); EG(static_cache_class_access_active) = false; EG(tracked_mutation_hooks_active) = false; @@ -1326,6 +1295,29 @@ static void zend_opcache_static_cache_register_accelerator_handlers(void) zend_accel_register_static_cache_handlers(&handlers); } +zend_result zend_opcache_static_cache_minit(void) +{ + zend_opcache_static_cache_context *previous_context; + + zend_opcache_static_cache_subsystem_disabled = false; + zend_opcache_static_cache_subsystem_failure_reason = NULL; + zend_opcache_static_cache_safe_direct_classes_marked = false; + + zend_opcache_static_cache_register_classes(); + zend_opcache_static_cache_safe_direct_handlers_init(); + zend_opcache_static_cache_register_accelerator_handlers(); + + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + zend_opcache_static_cache_reset_storage(); + + zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); + zend_opcache_static_cache_reset_storage(); + + zend_opcache_static_cache_restore_context(previous_context); + + return SUCCESS; +} + void zend_opcache_static_cache_invalidate_all(void) { zend_opcache_static_cache_invalidate_all_context(&zend_opcache_static_cache_pinned_context_state); diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index 692699fc2be5..e0ac77e976b0 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -86,6 +86,7 @@ #define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_INVALID_SLOT UINT32_MAX #define ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES 256U +#define ZEND_OPCACHE_STATIC_CACHE_LOW_MEMORY_COMPACT_THRESHOLD (3U * 1024U * 1024U) #define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_EMPTY 0 #define ZEND_OPCACHE_STATIC_CACHE_LOOKUP_HIT 1 @@ -421,8 +422,6 @@ bool zend_opcache_static_cache_header_init_locked(void); void zend_opcache_static_cache_free_locked(uint32_t payload_offset); uint32_t zend_opcache_static_cache_alloc_locked(size_t size, const void *source); bool zend_opcache_static_cache_compact_to_fit_locked(size_t size); -bool zend_opcache_static_cache_compact_available_locked(void); -void zend_opcache_static_cache_compact_after_request_shutdown(void); bool zend_opcache_static_cache_startup_storage_before_request(void); void zend_opcache_static_cache_shutdown_storage(void); void zend_opcache_static_cache_ensure_ready(void); diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 5ac60e52cc6c..7724b3f9608a 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -1395,7 +1395,7 @@ static uint32_t zend_opcache_static_cache_calculate_capacity(size_t size) return (uint32_t) capacity; } -static uint32_t zend_opcache_static_cache_used_end_offset_locked(const zend_opcache_static_cache_header *header) +static zend_always_inline uint32_t zend_opcache_static_cache_used_end_offset_locked(const zend_opcache_static_cache_header *header) { return header->data_offset + header->next_free; } @@ -1594,7 +1594,7 @@ static bool zend_opcache_static_cache_startup_storage(void) return true; } -static bool zend_opcache_static_cache_payload_size_to_block_size(size_t size, uint32_t *block_size) +static zend_always_inline bool zend_opcache_static_cache_payload_size_to_block_size(size_t size, uint32_t *block_size) { size_t aligned_size; @@ -1612,7 +1612,7 @@ static bool zend_opcache_static_cache_payload_size_to_block_size(size_t size, ui return true; } -static bool zend_opcache_static_cache_offset_in_block(uint32_t offset, uint32_t block_offset, uint32_t block_size) +static zend_always_inline bool zend_opcache_static_cache_offset_in_block(uint32_t offset, uint32_t block_offset, uint32_t block_size) { return offset >= block_offset + sizeof(zend_opcache_static_cache_block) && offset < block_offset + block_size; } @@ -1761,6 +1761,115 @@ static bool zend_opcache_static_cache_compaction_can_fit_locked( return would_move && max_free_size >= required_block_size; } +static bool zend_opcache_static_cache_compact_movable_blocks_locked(zend_opcache_static_cache_header *header) +{ + zend_opcache_static_cache_block *block, *free_block; + uint32_t used_end, offset, next_offset, block_size, write_offset, + previous_block_size = 0, last_block_offset = 0, free_size; + bool moved = false, movable; + + used_end = header->data_offset + header->next_free; + offset = header->data_offset; + write_offset = header->data_offset; + header->free_list = 0; + + while (offset < used_end) { + block = zend_opcache_static_cache_block_ptr(offset); + block_size = block->size; + if (block_size < ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + 1) || + block_size > used_end - offset + ) { + return false; + } + + next_offset = offset + block_size; + if (zend_opcache_static_cache_block_is_free(block)) { + offset = next_offset; + continue; + } + + movable = zend_opcache_static_cache_block_is_movable_locked(header, offset, block_size); + if (!movable) { + if (write_offset < offset) { + free_block = zend_opcache_static_cache_block_ptr(write_offset); + free_size = offset - write_offset; + + free_block->size = free_size; + free_block->prev_size = previous_block_size; + free_block->next_free = 0; + free_block->prev_free = 0; + free_block->flags = ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE; + zend_opcache_static_cache_free_list_insert_locked(header, write_offset); + previous_block_size = free_size; + last_block_offset = write_offset; + } + + block->prev_size = previous_block_size; + block->next_free = 0; + block->prev_free = 0; + zend_opcache_static_cache_block_mark_used(block); + previous_block_size = block_size; + last_block_offset = offset; + write_offset = next_offset; + offset = next_offset; + continue; + } + + if (write_offset != offset) { + memmove(zend_opcache_static_cache_ptr(write_offset), block, block_size); + zend_opcache_static_cache_update_moved_block_entries_locked(header, offset, write_offset, block_size); + block = zend_opcache_static_cache_block_ptr(write_offset); + moved = true; + } + + block->prev_size = previous_block_size; + block->next_free = 0; + block->prev_free = 0; + zend_opcache_static_cache_block_mark_used(block); + previous_block_size = block_size; + last_block_offset = write_offset; + write_offset += block_size; + offset = next_offset; + } + + header->next_free = write_offset - header->data_offset; + header->last_block_offset = last_block_offset; + + if (moved) { + zend_opcache_static_cache_bump_mutation_epoch_locked(header); + } + + return moved; +} + +static bool zend_opcache_static_cache_compact_if_low_memory_locked( + zend_opcache_static_cache_header *header, + uint32_t allocating_block_size +) +{ + uint32_t tail_remaining; + + if (!zend_opcache_static_cache_active_context()->clear_on_pressure || + header->free_list == 0 || + header->next_free > header->data_size + ) { + return false; + } + + tail_remaining = header->data_size - header->next_free; + if (tail_remaining >= ZEND_OPCACHE_STATIC_CACHE_LOW_MEMORY_COMPACT_THRESHOLD && + allocating_block_size <= tail_remaining - ZEND_OPCACHE_STATIC_CACHE_LOW_MEMORY_COMPACT_THRESHOLD + ) { + return false; + } + + if (!zend_opcache_static_cache_compaction_can_fit_locked(header, 0)) { + return false; + } + + return zend_opcache_static_cache_compact_movable_blocks_locked(header); +} + void zend_opcache_static_cache_reset_runtime(void) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); @@ -1900,6 +2009,7 @@ uint32_t zend_opcache_static_cache_alloc_locked(size_t size, const void *source) } total_size = (uint32_t) aligned_size; + zend_opcache_static_cache_compact_if_low_memory_locked(header, total_size); free_offset_ptr = &header->free_list; while (*free_offset_ptr != 0) { @@ -1961,87 +2071,6 @@ uint32_t zend_opcache_static_cache_alloc_locked(size_t size, const void *source) return block_offset + (uint32_t) sizeof(zend_opcache_static_cache_block); } -static bool zend_opcache_static_cache_compact_movable_blocks_locked(zend_opcache_static_cache_header *header) -{ - zend_opcache_static_cache_block *block, *free_block; - uint32_t used_end, offset, next_offset, block_size, write_offset, - previous_block_size = 0, last_block_offset = 0, free_size; - bool moved = false, movable; - - used_end = header->data_offset + header->next_free; - offset = header->data_offset; - write_offset = header->data_offset; - header->free_list = 0; - - while (offset < used_end) { - block = zend_opcache_static_cache_block_ptr(offset); - block_size = block->size; - if (block_size < ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + 1) || - block_size > used_end - offset - ) { - return false; - } - - next_offset = offset + block_size; - if (zend_opcache_static_cache_block_is_free(block)) { - offset = next_offset; - continue; - } - - movable = zend_opcache_static_cache_block_is_movable_locked(header, offset, block_size); - if (!movable) { - if (write_offset < offset) { - free_block = zend_opcache_static_cache_block_ptr(write_offset); - free_size = offset - write_offset; - - free_block->size = free_size; - free_block->prev_size = previous_block_size; - free_block->next_free = 0; - free_block->prev_free = 0; - free_block->flags = ZEND_OPCACHE_STATIC_CACHE_BLOCK_FREE; - zend_opcache_static_cache_free_list_insert_locked(header, write_offset); - previous_block_size = free_size; - last_block_offset = write_offset; - } - - block->prev_size = previous_block_size; - block->next_free = 0; - block->prev_free = 0; - zend_opcache_static_cache_block_mark_used(block); - previous_block_size = block_size; - last_block_offset = offset; - write_offset = next_offset; - offset = next_offset; - continue; - } - - if (write_offset != offset) { - memmove(zend_opcache_static_cache_ptr(write_offset), block, block_size); - zend_opcache_static_cache_update_moved_block_entries_locked(header, offset, write_offset, block_size); - block = zend_opcache_static_cache_block_ptr(write_offset); - moved = true; - } - - block->prev_size = previous_block_size; - block->next_free = 0; - block->prev_free = 0; - zend_opcache_static_cache_block_mark_used(block); - previous_block_size = block_size; - last_block_offset = write_offset; - write_offset += block_size; - offset = next_offset; - } - - header->next_free = write_offset - header->data_offset; - header->last_block_offset = last_block_offset; - - if (moved) { - zend_opcache_static_cache_bump_mutation_epoch_locked(header); - } - - return moved; -} - bool zend_opcache_static_cache_compact_to_fit_locked(size_t size) { zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); @@ -2061,43 +2090,6 @@ bool zend_opcache_static_cache_compact_to_fit_locked(size_t size) return zend_opcache_static_cache_compact_movable_blocks_locked(header); } -bool zend_opcache_static_cache_compact_available_locked(void) -{ - zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); - - if (!header || - !zend_opcache_static_cache_header_init_locked() || - header->free_list == 0 || - !zend_opcache_static_cache_compaction_can_fit_locked(header, 0) - ) { - return false; - } - - return zend_opcache_static_cache_compact_movable_blocks_locked(header); -} - -static void zend_opcache_static_cache_compact_context_after_request_shutdown(zend_opcache_static_cache_context *context) -{ - zend_opcache_static_cache_context *previous_context; - - if (!zend_opcache_static_cache_context_runtime(context)->available) { - return; - } - - previous_context = zend_opcache_static_cache_activate_context(context); - if (zend_opcache_static_cache_wlock()) { - zend_opcache_static_cache_compact_available_locked(); - zend_opcache_static_cache_unlock(); - } - zend_opcache_static_cache_restore_context(previous_context); -} - -void zend_opcache_static_cache_compact_after_request_shutdown(void) -{ - zend_opcache_static_cache_compact_context_after_request_shutdown(&zend_opcache_static_cache_volatile_context_state); - zend_opcache_static_cache_compact_context_after_request_shutdown(&zend_opcache_static_cache_pinned_context_state); -} - bool zend_opcache_static_cache_startup_storage_before_request(void) { zend_opcache_static_cache_storage *storage = &zend_opcache_static_cache_active_context()->storage; From 767b86943a5c06ff647cde7688b95f2d01442fa4 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 06:10:26 +0000 Subject: [PATCH 18/29] fix: opcache static cache hooks --- Zend/tests/static_cache_hook_safety.phpt | 50 ++++++++++++++++++++++++ Zend/zend_globals.h | 2 +- Zend/zend_object_handlers.c | 4 +- Zend/zend_vm_def.h | 8 +++- Zend/zend_vm_execute.h | 16 ++++++-- 5 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/static_cache_hook_safety.phpt diff --git a/Zend/tests/static_cache_hook_safety.phpt b/Zend/tests/static_cache_hook_safety.phpt new file mode 100644 index 000000000000..ebb82d20fd28 --- /dev/null +++ b/Zend/tests/static_cache_hook_safety.phpt @@ -0,0 +1,50 @@ +--TEST-- +OPcache static cache init hooks are request guarded +--SKIPIF-- + +--FILE-- + 2, 'zend_vm_execute.h' => 4] as $file => $minimum) { + requireGuardedHook( + file_get_contents($zendDir . '/' . $file), + $file, + 'zend_function_init_statics_hook', + $minimum + ); +} + +echo "OK\n"; + +?> +--EXPECT-- +OK diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index bae276f93ca2..13e81ae46424 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -269,7 +269,7 @@ struct _zend_executor_globals { bool active; - bool static_cache_class_access_active; /* fast guard for zend_class_static_access_hook */ + bool static_cache_class_access_active; /* fast guard for OPcache static cache hooks */ bool tracked_mutation_hooks_active; /* fast guard for tracked array/object mutation hooks */ uint8_t flags; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index bfc37c672e14..c9bafd250721 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -2095,7 +2095,9 @@ ZEND_API void zend_class_init_statics(zend_class_entry *class_type) /* {{{ */ } } - if (UNEXPECTED(zend_class_init_statics_hook != NULL)) { + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_class_init_statics_hook != NULL) + ) { zend_class_init_statics_hook(class_type); } } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index c485a4be53b3..b67e3fa5169f 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9267,7 +9267,9 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, ANY, REF) SAVE_OPLINE(); - if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_function_init_statics_hook != NULL) + ) { zend_function_init_statics_hook(execute_data); } @@ -9322,7 +9324,9 @@ ZEND_VM_HANDLER(203, ZEND_BIND_INIT_STATIC_OR_JMP, CV, JMP_ADDR) variable_ptr = GET_OP1_ZVAL_PTR_PTR_UNDEF(BP_VAR_W); - if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_function_init_statics_hook != NULL) + ) { zend_function_init_statics_hook(execute_data); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 07d31fcf2e80..02f850b35001 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -41232,7 +41232,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_BIND_STATIC_S SAVE_OPLINE(); - if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_function_init_statics_hook != NULL) + ) { zend_function_init_statics_hook(execute_data); } @@ -41287,7 +41289,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_BIND_INIT_STA variable_ptr = EX_VAR(opline->op1.var); - if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_function_init_statics_hook != NULL) + ) { zend_function_init_statics_hook(execute_data); } @@ -94294,7 +94298,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_BIND_STATIC_SPEC_C SAVE_OPLINE(); - if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_function_init_statics_hook != NULL) + ) { zend_function_init_statics_hook(execute_data); } @@ -94349,7 +94355,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_BIND_INIT_STATIC_O variable_ptr = EX_VAR(opline->op1.var); - if (UNEXPECTED(zend_function_init_statics_hook != NULL)) { + if (UNEXPECTED(EG(static_cache_class_access_active) && + zend_function_init_statics_hook != NULL) + ) { zend_function_init_statics_hook(execute_data); } From c61e93a86813b93fa6934e421fbb513c947fd784 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 06:58:47 +0000 Subject: [PATCH 19/29] fix: invalid opcache SHM initialization --- .../static_cache_default_jit_startup_001.phpt | 29 ++ ext/opcache/zend_static_cache.c | 4 + ext/opcache/zend_static_cache_storage.c | 271 +++++++++++------- 3 files changed, 199 insertions(+), 105 deletions(-) create mode 100644 ext/opcache/tests/jit/static_cache_default_jit_startup_001.phpt diff --git a/ext/opcache/tests/jit/static_cache_default_jit_startup_001.phpt b/ext/opcache/tests/jit/static_cache_default_jit_startup_001.phpt new file mode 100644 index 000000000000..e9e37ae9b220 --- /dev/null +++ b/ext/opcache/tests/jit/static_cache_default_jit_startup_001.phpt @@ -0,0 +1,29 @@ +--TEST-- +OPcache starts with default static cache memory and tracing JIT +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.jit_buffer_size=64M +opcache.protect_memory=1 +--FILE-- +enabled); +var_dump($status['pinned_cache']->enabled); +echo "OK\n"; + +?> +--EXPECT-- +int(8388608) +int(8388608) +bool(true) +bool(true) +OK diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index 17d2dcf57579..30d39c00e8d0 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -1266,6 +1266,10 @@ static zend_result zend_opcache_static_cache_rinit(void) zend_result zend_opcache_static_cache_rshutdown(void) { zend_opcache_static_cache_clear_lookup_caches(); + + EG(static_cache_class_access_active) = false; + EG(tracked_mutation_hooks_active) = false; + zend_opcache_static_cache_request_shutdown(); zend_opcache_static_cache_release_request_entry_locks(); zend_opcache_static_cache_release_request_local_slots(); diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 7724b3f9608a..04f2f512f8b8 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -35,11 +35,99 @@ # ifdef HAVE_UNISTD_H # include # endif -# if defined(__linux__) && defined(HAVE_MEMFD_CREATE) +# if defined(USE_MMAP) || (defined(__linux__) && defined(HAVE_MEMFD_CREATE)) # include # endif #endif +#if defined(USE_MMAP) && !defined(ZEND_WIN32) +# if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +# define MAP_ANONYMOUS MAP_ANON +# endif + +static zend_always_inline bool zend_opcache_static_cache_force_startup_failure(void) +{ + const char *value = getenv("OPCACHE_STATIC_CACHE_FORCE_STARTUP_FAILURE"); + + return value != NULL && value[0] != '\0' && value[0] != '0'; +} + +static zend_always_inline bool zend_opcache_static_cache_requires_pre_request_storage(void) +{ + return sapi_module.name != NULL && strcmp(sapi_module.name, "fpm-fcgi") == 0; +} + +static zend_always_inline void zend_opcache_static_cache_set_unavailable(const char *failure_reason, bool startup_failed) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); + + runtime->available = false; + runtime->startup_failed = startup_failed; + runtime->backend_initialized = context->storage.initialized; + runtime->failure_reason = failure_reason; +} + +static zend_always_inline void zend_opcache_static_cache_set_available(void) +{ + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); + + runtime->available = true; + runtime->startup_failed = false; + runtime->backend_initialized = context->storage.initialized; + runtime->failure_reason = NULL; +} + +static zend_always_inline HashTable **zend_opcache_static_cache_entry_locks_ptr_for_context(zend_opcache_static_cache_context *context) +{ + return context == &zend_opcache_static_cache_pinned_context_state + ? &zend_opcache_static_cache_pinned_entry_locks + : &zend_opcache_static_cache_volatile_entry_locks + ; +} + +static zend_always_inline uint32_t *zend_opcache_static_cache_entry_lock_counts_for_context(zend_opcache_static_cache_context *context) +{ + return context == &zend_opcache_static_cache_pinned_context_state + ? zend_opcache_static_cache_pinned_entry_lock_counts + : zend_opcache_static_cache_volatile_entry_lock_counts + ; +} + +static zend_always_inline uint32_t zend_opcache_static_cache_entry_lock_stripe(zend_string *key) +{ + return (uint32_t) (zend_string_hash_val(key) % ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES); +} + +static zend_always_inline uint32_t zend_opcache_static_cache_used_end_offset_locked(const zend_opcache_static_cache_header *header) +{ + return header->data_offset + header->next_free; +} + +static zend_always_inline bool zend_opcache_static_cache_payload_size_to_block_size(size_t size, uint32_t *block_size) +{ + size_t aligned_size; + + if (size == 0 || size > UINT32_MAX - sizeof(zend_opcache_static_cache_block)) { + return false; + } + + aligned_size = ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + size); + if (aligned_size > UINT32_MAX) { + return false; + } + + *block_size = (uint32_t) aligned_size; + + return true; +} + +static zend_always_inline bool zend_opcache_static_cache_offset_in_block(uint32_t offset, uint32_t block_offset, uint32_t block_size) +{ + return offset >= block_offset + sizeof(zend_opcache_static_cache_block) && offset < block_offset + block_size; +} + #ifdef ZEND_WIN32 # define ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE (2 * sizeof(void *)) # define ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_NAME "ZendOPcache.StaticCache.SharedMemoryArea" @@ -53,6 +141,80 @@ typedef struct _zend_opcache_static_cache_win32_segment { size_t mapping_size; } zend_opcache_static_cache_win32_segment; +static inline bool zend_opcache_static_cache_win32_set_segment( + zend_opcache_static_cache_win32_segment *segment, + HANDLE memfile, + void *mapping_base, + size_t mapping_size, + size_t requested_size +) +{ + segment->memfile = memfile; + segment->mapping_base = mapping_base; + segment->mapping_size = mapping_size; + segment->segment.p = (char *) mapping_base + ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE; + segment->segment.pos = 0; + segment->segment.size = requested_size; + + return true; +} +#endif + +static int zend_opcache_static_cache_mmap_create_segments( + size_t requested_size, + zend_shared_segment ***shared_segments_p, + int *shared_segments_count, + const char **error_in +) +{ + zend_shared_segment *segment; + void *mapping; + + mapping = mmap(NULL, requested_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (mapping == MAP_FAILED) { + *error_in = "mmap"; + return ALLOC_FAILURE; + } + + *shared_segments_count = 1; + *shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment *) + sizeof(zend_shared_segment)); + if (*shared_segments_p == NULL) { + munmap(mapping, requested_size); + *error_in = "calloc"; + return ALLOC_FAILURE; + } + + segment = (zend_shared_segment *) ((char *) *shared_segments_p + sizeof(zend_shared_segment *)); + (*shared_segments_p)[0] = segment; + + segment->p = mapping; + segment->pos = 0; + segment->size = requested_size; + segment->end = requested_size; + + return ALLOC_SUCCESS; +} + +static int zend_opcache_static_cache_mmap_detach_segment(zend_shared_segment *shared_segment) +{ + munmap(shared_segment->p, shared_segment->size); + + return 0; +} + +static size_t zend_opcache_static_cache_mmap_segment_type_size(void) +{ + return sizeof(zend_shared_segment); +} + +static const zend_shared_memory_handlers zend_opcache_static_cache_mmap_handlers = { + zend_opcache_static_cache_mmap_create_segments, + zend_opcache_static_cache_mmap_detach_segment, + zend_opcache_static_cache_mmap_segment_type_size +}; +#endif + +#ifdef ZEND_WIN32 static void zend_opcache_static_cache_win32_create_name(char *buffer, size_t buffer_size, const char *name, size_t unique_id) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); @@ -71,24 +233,6 @@ static void zend_opcache_static_cache_win32_create_name(char *buffer, size_t buf ); } -static bool zend_opcache_static_cache_win32_set_segment( - zend_opcache_static_cache_win32_segment *segment, - HANDLE memfile, - void *mapping_base, - size_t mapping_size, - size_t requested_size -) -{ - segment->memfile = memfile; - segment->mapping_base = mapping_base; - segment->mapping_size = mapping_size; - segment->segment.p = (char *) mapping_base + ZEND_OPCACHE_STATIC_CACHE_WIN32_MAPPING_PREFIX_SIZE; - segment->segment.pos = 0; - segment->segment.size = requested_size; - - return true; -} - static int zend_opcache_static_cache_win32_reattach_segment( zend_opcache_static_cache_win32_segment *segment, HANDLE memfile, @@ -357,8 +501,8 @@ static const zend_shared_memory_handlers zend_opcache_static_cache_win32_handler #endif static const zend_shared_memory_handler_entry zend_opcache_static_cache_handler_table[] = { -#ifdef USE_MMAP - { "mmap", &zend_alloc_mmap_handlers }, +#if defined(USE_MMAP) && !defined(ZEND_WIN32) + { "mmap", &zend_opcache_static_cache_mmap_handlers }, #endif #ifdef USE_SHM { "shm", &zend_alloc_shm_handlers }, @@ -379,41 +523,7 @@ static ZEND_EXT_TLS bool zend_opcache_static_cache_entry_locks_process_is_fork_c #endif #endif -static zend_always_inline bool zend_opcache_static_cache_force_startup_failure(void) -{ - const char *value = getenv("OPCACHE_STATIC_CACHE_FORCE_STARTUP_FAILURE"); - - return value != NULL && value[0] != '\0' && value[0] != '0'; -} - -static zend_always_inline bool zend_opcache_static_cache_requires_pre_request_storage(void) -{ - return sapi_module.name != NULL && strcmp(sapi_module.name, "fpm-fcgi") == 0; -} - -static zend_always_inline void zend_opcache_static_cache_set_unavailable(const char *failure_reason, bool startup_failed) -{ - zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); - zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); - - runtime->available = false; - runtime->startup_failed = startup_failed; - runtime->backend_initialized = context->storage.initialized; - runtime->failure_reason = failure_reason; -} - -static zend_always_inline void zend_opcache_static_cache_set_available(void) -{ - zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); - zend_opcache_static_cache_runtime *runtime = zend_opcache_static_cache_context_runtime(context); - - runtime->available = true; - runtime->startup_failed = false; - runtime->backend_initialized = context->storage.initialized; - runtime->failure_reason = NULL; -} - -static zend_always_inline void zend_opcache_static_cache_cleanup_segments(const zend_shared_memory_handlers *handler, zend_shared_segment **segments, int segment_count) +static void zend_opcache_static_cache_cleanup_segments(const zend_shared_memory_handlers *handler, zend_shared_segment **segments, int segment_count) { int index; @@ -869,27 +979,6 @@ static void zend_opcache_static_cache_unlock_impl(void) } #endif -static HashTable **zend_opcache_static_cache_entry_locks_ptr_for_context(zend_opcache_static_cache_context *context) -{ - return context == &zend_opcache_static_cache_pinned_context_state - ? &zend_opcache_static_cache_pinned_entry_locks - : &zend_opcache_static_cache_volatile_entry_locks - ; -} - -static uint32_t *zend_opcache_static_cache_entry_lock_counts_for_context(zend_opcache_static_cache_context *context) -{ - return context == &zend_opcache_static_cache_pinned_context_state - ? zend_opcache_static_cache_pinned_entry_lock_counts - : zend_opcache_static_cache_volatile_entry_lock_counts - ; -} - -static uint32_t zend_opcache_static_cache_entry_lock_stripe(zend_string *key) -{ - return (uint32_t) (zend_string_hash_val(key) % ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_STRIPES); -} - #if defined(ZTS) && !defined(ZEND_WIN32) static void zend_opcache_static_cache_entry_locks_reinit_after_fork(zend_opcache_static_cache_storage *storage) { @@ -1395,11 +1484,6 @@ static uint32_t zend_opcache_static_cache_calculate_capacity(size_t size) return (uint32_t) capacity; } -static zend_always_inline uint32_t zend_opcache_static_cache_used_end_offset_locked(const zend_opcache_static_cache_header *header) -{ - return header->data_offset + header->next_free; -} - static void zend_opcache_static_cache_free_list_remove_locked(zend_opcache_static_cache_header *header, uint32_t block_offset) { zend_opcache_static_cache_block *block = zend_opcache_static_cache_block_ptr(block_offset); @@ -1594,29 +1678,6 @@ static bool zend_opcache_static_cache_startup_storage(void) return true; } -static zend_always_inline bool zend_opcache_static_cache_payload_size_to_block_size(size_t size, uint32_t *block_size) -{ - size_t aligned_size; - - if (size == 0 || size > UINT32_MAX - sizeof(zend_opcache_static_cache_block)) { - return false; - } - - aligned_size = ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + size); - if (aligned_size > UINT32_MAX) { - return false; - } - - *block_size = (uint32_t) aligned_size; - - return true; -} - -static zend_always_inline bool zend_opcache_static_cache_offset_in_block(uint32_t offset, uint32_t block_offset, uint32_t block_size) -{ - return offset >= block_offset + sizeof(zend_opcache_static_cache_block) && offset < block_offset + block_size; -} - static bool zend_opcache_static_cache_block_is_movable_locked( zend_opcache_static_cache_header *header, uint32_t block_offset, From 0422eda34a56c53e90bcffa009364e70b54b284f Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 08:34:56 +0000 Subject: [PATCH 20/29] refactor and fixes --- ext/date/config.w32 | 1 + ext/date/config0.m4 | 2 + ext/date/php_date.c | 443 ++++++++++-------- ext/date/php_date.h | 4 - ext/opcache/config.m4 | 1 - ext/opcache/config.w32 | 1 - ext/opcache/opcache.stub.php | 5 - ext/opcache/opcache_arginfo.h | 17 +- ..._volatile_cache_direct_cache_safe_001.phpt | 2 +- ...ested_shared_object_copy_on_write_001.phpt | 2 +- ...abled_without_static_cache_memory_001.phpt | 13 +- ...he_request_local_safe_direct_slot_001.phpt | 2 +- ..._volatile_cache_direct_cache_safe_001.phpt | 7 +- ..._volatile_cache_direct_cache_safe_002.phpt | 7 +- ..._volatile_cache_direct_cache_safe_003.phpt | 11 +- ..._volatile_cache_direct_cache_safe_004.phpt | 2 +- ...tile_cache_direct_cache_safe_fork_001.phpt | 2 +- ...ache_direct_cache_safe_unstorable_001.phpt | 2 +- ...cache_direct_cache_safe_visibility_001.inc | 6 - ...ache_direct_cache_safe_visibility_001.phpt | 28 +- ...latile_cache_materialization_fork_002.phpt | 2 +- ..._cache_volatile_cache_zts_threads_002.phpt | 2 +- ext/opcache/zend_opcache_serializer.h | 10 - ext/opcache/zend_static_cache.c | 233 +-------- ext/opcache/zend_static_cache.h | 4 + ext/opcache/zend_static_cache_internal.h | 7 - ext/spl/config.m4 | 1 + ext/spl/config.w32 | 1 + ext/spl/spl_array.c | 11 +- ext/spl/spl_array.h | 4 - ext/spl/spl_fixedarray.c | 9 +- ext/spl/spl_fixedarray.h | 4 - 32 files changed, 301 insertions(+), 545 deletions(-) delete mode 100644 ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc diff --git a/ext/date/config.w32 b/ext/date/config.w32 index b053e27aae35..40c5d8864fdf 100644 --- a/ext/date/config.w32 +++ b/ext/date/config.w32 @@ -2,6 +2,7 @@ EXTENSION("date", "php_date.c", false, "/Iext/date/lib /DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 /DHAVE_TIMELIB_CONFIG_H=1"); PHP_DATE = "yes"; +ADD_EXTENSION_DEP('date', 'opcache'); ADD_SOURCES("ext/date/lib", "astro.c timelib.c dow.c parse_date.c parse_posix.c parse_tz.c tm2unixtime.c unixtime2tm.c parse_iso_intervals.c interval.c", "date"); ADD_FLAG('CFLAGS_DATE', "/wd4244"); diff --git a/ext/date/config0.m4 b/ext/date/config0.m4 index c78fcb78e15e..bb89091ac635 100644 --- a/ext/date/config0.m4 +++ b/ext/date/config0.m4 @@ -22,6 +22,8 @@ PHP_NEW_EXTENSION([date], [no],, [$PHP_DATE_CFLAGS]) +PHP_ADD_EXTENSION_DEP(date, opcache) + PHP_ADD_SOURCES([$ext_dir], [$timelib_sources], [$PHP_TIMELIB_CFLAGS]) PHP_ADD_BUILD_DIR([$ext_builddir/lib], [1]) diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 3d24f0d2a8fa..cd09002c6fd1 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -17,7 +17,7 @@ #include "php_ini.h" #include "ext/standard/info.h" #include "ext/standard/php_versioning.h" -#include "ext/opcache/zend_static_cache.h" +#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ #include "php_date.h" #include "zend_attributes.h" #include "zend_execute.h" @@ -318,6 +318,242 @@ static void date_throw_uninitialized_error(zend_class_entry *ce) } } +static void date_object_to_hash(php_date_obj *dateobj, HashTable *props); +static void date_timezone_object_to_hash(php_timezone_obj *tzobj, HashTable *props); +static bool php_date_initialize_from_hash(php_date_obj **dateobj, const HashTable *myht); +static bool php_date_timezone_initialize_from_hash(php_timezone_obj **tzobj, const HashTable *myht); +static void php_date_interval_initialize_from_hash(php_interval_obj *intobj, const HashTable *myht); +static void php_date_direct_cache_add_assoc_int64(zval *array_zv, const char *name, int64_t value); + +static bool php_date_copy_direct_cache_state( + void *context, + zend_object *old_object, + zend_object *new_object, + zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value) +{ + (void) context; + (void) clone_value; + + php_date_obj *new_obj, *old_obj; + php_timezone_obj *new_tzobj, *old_tzobj; + php_interval_obj *new_intervalobj, *old_intervalobj; + + if (instanceof_function(old_object->ce, date_ce_date) || + instanceof_function(old_object->ce, date_ce_immutable) + ) { + old_obj = php_date_obj_from_obj(old_object); + new_obj = php_date_obj_from_obj(new_object); + + if (old_obj->time == NULL) { + return true; + } + + new_obj->time = timelib_time_clone(old_obj->time); + + return new_obj->time != NULL; + } + + if (instanceof_function(old_object->ce, date_ce_timezone)) { + old_tzobj = php_timezone_obj_from_obj(old_object); + new_tzobj = php_timezone_obj_from_obj(new_object); + + if (!old_tzobj->initialized) { + return true; + } + + new_tzobj->type = old_tzobj->type; + new_tzobj->initialized = true; + switch (new_tzobj->type) { + case TIMELIB_ZONETYPE_ID: + new_tzobj->tzi.tz = old_tzobj->tzi.tz; + + return true; + case TIMELIB_ZONETYPE_OFFSET: + new_tzobj->tzi.utc_offset = old_tzobj->tzi.utc_offset; + + return true; + case TIMELIB_ZONETYPE_ABBR: + new_tzobj->tzi.z.utc_offset = old_tzobj->tzi.z.utc_offset; + new_tzobj->tzi.z.dst = old_tzobj->tzi.z.dst; + new_tzobj->tzi.z.abbr = old_tzobj->tzi.z.abbr != NULL + ? timelib_strdup(old_tzobj->tzi.z.abbr) + : NULL + ; + + return old_tzobj->tzi.z.abbr == NULL || new_tzobj->tzi.z.abbr != NULL; + default: + return false; + } + } + + if (instanceof_function(old_object->ce, date_ce_interval)) { + old_intervalobj = php_interval_obj_from_obj(old_object); + new_intervalobj = php_interval_obj_from_obj(new_object); + + new_intervalobj->civil_or_wall = old_intervalobj->civil_or_wall; + new_intervalobj->from_string = old_intervalobj->from_string; + + if (old_intervalobj->date_string != NULL) { + new_intervalobj->date_string = zend_string_copy(old_intervalobj->date_string); + } + + new_intervalobj->initialized = old_intervalobj->initialized; + + if (old_intervalobj->diff != NULL) { + new_intervalobj->diff = timelib_rel_time_clone(old_intervalobj->diff); + + return new_intervalobj->diff != NULL; + } + + return true; + } + + return false; +} + +static bool php_date_serialize_direct_cache_state(const zval *object, zval *state) +{ + php_date_obj *dateobj; + php_timezone_obj *tzobj; + php_interval_obj *intervalobj; + + ZVAL_UNDEF(state); + + if (instanceof_function(Z_OBJCE_P(object), date_ce_date) || + instanceof_function(Z_OBJCE_P(object), date_ce_immutable) + ) { + dateobj = Z_PHPDATE_P((zval *) object); + + if (dateobj->time == NULL || !dateobj->time->is_localtime) { + return false; + } + + array_init_size(state, 3); + date_object_to_hash(dateobj, Z_ARRVAL_P(state)); + + return true; + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_timezone)) { + tzobj = Z_PHPTIMEZONE_P((zval *) object); + + if (!tzobj->initialized) { + return false; + } + + array_init_size(state, 2); + date_timezone_object_to_hash(tzobj, Z_ARRVAL_P(state)); + + return true; + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_interval)) { + intervalobj = Z_PHPINTERVAL_P((zval *) object); + + if (!intervalobj->initialized || intervalobj->diff == NULL) { + return false; + } + + if (intervalobj->from_string) { + if (intervalobj->date_string == NULL) { + return false; + } + + array_init_size(state, 2); + add_assoc_bool(state, "from_string", true); + add_assoc_str(state, "date_string", zend_string_copy(intervalobj->date_string)); + + return true; + } + + array_init_size(state, 18); + add_assoc_long(state, "y", intervalobj->diff->y); + add_assoc_long(state, "m", intervalobj->diff->m); + add_assoc_long(state, "d", intervalobj->diff->d); + add_assoc_long(state, "h", intervalobj->diff->h); + add_assoc_long(state, "i", intervalobj->diff->i); + add_assoc_long(state, "s", intervalobj->diff->s); + add_assoc_double(state, "f", (double) intervalobj->diff->us / 1000000.0); + add_assoc_long(state, "invert", intervalobj->diff->invert); + + if (intervalobj->diff->days != TIMELIB_UNSET) { + php_date_direct_cache_add_assoc_int64(state, "days", intervalobj->diff->days); + } else { + add_assoc_bool(state, "days", false); + } + + add_assoc_bool(state, "from_string", false); + add_assoc_long(state, "weekday", intervalobj->diff->weekday); + add_assoc_long(state, "weekday_behavior", intervalobj->diff->weekday_behavior); + add_assoc_long(state, "first_last_day_of", intervalobj->diff->first_last_day_of); + add_assoc_long(state, "special_type", intervalobj->diff->special.type); + php_date_direct_cache_add_assoc_int64(state, "special_amount", intervalobj->diff->special.amount); + add_assoc_long(state, "have_weekday_relative", intervalobj->diff->have_weekday_relative); + add_assoc_long(state, "have_special_relative", intervalobj->diff->have_special_relative); + add_assoc_long(state, "civil_or_wall", intervalobj->civil_or_wall); + + return true; + } + + return false; +} + +static bool php_date_unserialize_direct_cache_state(zval *object, zval *state) +{ + php_date_obj *dateobj; + php_timezone_obj *tzobj; + + if (Z_TYPE_P(state) != IS_ARRAY) { + return false; + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_date) || + instanceof_function(Z_OBJCE_P(object), date_ce_immutable) + ) { + dateobj = Z_PHPDATE_P(object); + + return php_date_initialize_from_hash(&dateobj, Z_ARRVAL_P(state)); + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_timezone)) { + tzobj = Z_PHPTIMEZONE_P(object); + + return php_date_timezone_initialize_from_hash(&tzobj, Z_ARRVAL_P(state)); + } + + if (instanceof_function(Z_OBJCE_P(object), date_ce_interval)) { + php_date_interval_initialize_from_hash(Z_PHPINTERVAL_P(object), Z_ARRVAL_P(state)); + + return !EG(exception); + } + + return false; +} + +static const zend_opcache_static_cache_safe_direct_handlers *php_date_get_direct_cache_handlers(void) +{ + static const zend_opcache_static_cache_safe_direct_handlers handlers = { + true, + { false, NULL, NULL }, + php_date_copy_direct_cache_state, + NULL, + php_date_serialize_direct_cache_state, + php_date_unserialize_direct_cache_state + }; + + return &handlers; +} + +static void php_date_register_direct_cache_handlers(void) +{ + const zend_opcache_static_cache_safe_direct_handlers *handlers = php_date_get_direct_cache_handlers(); + + zend_opcache_static_cache_safe_direct_register_class(date_ce_date, handlers); + zend_opcache_static_cache_safe_direct_register_class(date_ce_immutable, handlers); + zend_opcache_static_cache_safe_direct_register_class(date_ce_timezone, handlers); + zend_opcache_static_cache_safe_direct_register_class(date_ce_interval, handlers); +} + #define DATE_CHECK_INITIALIZED(member, ce) \ if (UNEXPECTED(!member)) { \ date_throw_uninitialized_error(ce); \ @@ -453,6 +689,9 @@ PHP_MINIT_FUNCTION(date) php_date_global_timezone_db = NULL; php_date_global_timezone_db_enabled = 0; DATEG(last_errors) = NULL; + + php_date_register_direct_cache_handlers(); + return SUCCESS; } /* }}} */ @@ -2196,84 +2435,6 @@ static void date_interval_object_to_hash(php_interval_obj *intervalobj, HashTabl #undef PHP_DATE_INTERVAL_ADD_PROPERTY } -static bool php_date_initialize_from_hash(php_date_obj **dateobj, const HashTable *myht); -static bool php_date_timezone_initialize_from_hash(php_timezone_obj **tzobj, const HashTable *myht); -static void php_date_interval_initialize_from_hash(php_interval_obj *intobj, const HashTable *myht); - -static bool php_date_copy_direct_cache_state( - void *context, - zend_object *old_object, - zend_object *new_object, - zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value) -{ - (void) context; - (void) clone_value; - - if (instanceof_function(old_object->ce, date_ce_date) || - instanceof_function(old_object->ce, date_ce_immutable)) { - php_date_obj *old_obj = php_date_obj_from_obj(old_object); - php_date_obj *new_obj = php_date_obj_from_obj(new_object); - - if (old_obj->time == NULL) { - return true; - } - - new_obj->time = timelib_time_clone(old_obj->time); - - return new_obj->time != NULL; - } - - if (instanceof_function(old_object->ce, date_ce_timezone)) { - php_timezone_obj *old_obj = php_timezone_obj_from_obj(old_object); - php_timezone_obj *new_obj = php_timezone_obj_from_obj(new_object); - - if (!old_obj->initialized) { - return true; - } - - new_obj->type = old_obj->type; - new_obj->initialized = true; - switch (new_obj->type) { - case TIMELIB_ZONETYPE_ID: - new_obj->tzi.tz = old_obj->tzi.tz; - return true; - case TIMELIB_ZONETYPE_OFFSET: - new_obj->tzi.utc_offset = old_obj->tzi.utc_offset; - return true; - case TIMELIB_ZONETYPE_ABBR: - new_obj->tzi.z.utc_offset = old_obj->tzi.z.utc_offset; - new_obj->tzi.z.dst = old_obj->tzi.z.dst; - new_obj->tzi.z.abbr = old_obj->tzi.z.abbr != NULL - ? timelib_strdup(old_obj->tzi.z.abbr) - : NULL; - return old_obj->tzi.z.abbr == NULL || new_obj->tzi.z.abbr != NULL; - default: - return false; - } - } - - if (instanceof_function(old_object->ce, date_ce_interval)) { - php_interval_obj *old_obj = php_interval_obj_from_obj(old_object); - php_interval_obj *new_obj = php_interval_obj_from_obj(new_object); - - new_obj->civil_or_wall = old_obj->civil_or_wall; - new_obj->from_string = old_obj->from_string; - if (old_obj->date_string != NULL) { - new_obj->date_string = zend_string_copy(old_obj->date_string); - } - new_obj->initialized = old_obj->initialized; - if (old_obj->diff != NULL) { - new_obj->diff = timelib_rel_time_clone(old_obj->diff); - - return new_obj->diff != NULL; - } - - return true; - } - - return false; -} - static void php_date_direct_cache_add_assoc_int64(zval *array_zv, const char *name, int64_t value) { #if PHP_DATE_SIZEOF_LONG == 8 @@ -2283,130 +2444,6 @@ static void php_date_direct_cache_add_assoc_int64(zval *array_zv, const char *na #endif } -static bool php_date_serialize_direct_cache_state(const zval *object, zval *state) -{ - ZVAL_UNDEF(state); - - if (instanceof_function(Z_OBJCE_P(object), date_ce_date) || - instanceof_function(Z_OBJCE_P(object), date_ce_immutable)) { - php_date_obj *dateobj = Z_PHPDATE_P((zval *) object); - - if (dateobj->time == NULL || !dateobj->time->is_localtime) { - return false; - } - - array_init_size(state, 3); - date_object_to_hash(dateobj, Z_ARRVAL_P(state)); - - return true; - } - - if (instanceof_function(Z_OBJCE_P(object), date_ce_timezone)) { - php_timezone_obj *tzobj = Z_PHPTIMEZONE_P((zval *) object); - - if (!tzobj->initialized) { - return false; - } - - array_init_size(state, 2); - date_timezone_object_to_hash(tzobj, Z_ARRVAL_P(state)); - - return true; - } - - if (instanceof_function(Z_OBJCE_P(object), date_ce_interval)) { - php_interval_obj *intervalobj = Z_PHPINTERVAL_P((zval *) object); - - if (!intervalobj->initialized || intervalobj->diff == NULL) { - return false; - } - - if (intervalobj->from_string) { - if (intervalobj->date_string == NULL) { - return false; - } - - array_init_size(state, 2); - add_assoc_bool(state, "from_string", true); - add_assoc_str(state, "date_string", zend_string_copy(intervalobj->date_string)); - - return true; - } - - array_init_size(state, 18); - add_assoc_long(state, "y", intervalobj->diff->y); - add_assoc_long(state, "m", intervalobj->diff->m); - add_assoc_long(state, "d", intervalobj->diff->d); - add_assoc_long(state, "h", intervalobj->diff->h); - add_assoc_long(state, "i", intervalobj->diff->i); - add_assoc_long(state, "s", intervalobj->diff->s); - add_assoc_double(state, "f", (double) intervalobj->diff->us / 1000000.0); - add_assoc_long(state, "invert", intervalobj->diff->invert); - - if (intervalobj->diff->days != TIMELIB_UNSET) { - php_date_direct_cache_add_assoc_int64(state, "days", intervalobj->diff->days); - } else { - add_assoc_bool(state, "days", false); - } - - add_assoc_bool(state, "from_string", false); - add_assoc_long(state, "weekday", intervalobj->diff->weekday); - add_assoc_long(state, "weekday_behavior", intervalobj->diff->weekday_behavior); - add_assoc_long(state, "first_last_day_of", intervalobj->diff->first_last_day_of); - add_assoc_long(state, "special_type", intervalobj->diff->special.type); - php_date_direct_cache_add_assoc_int64(state, "special_amount", intervalobj->diff->special.amount); - add_assoc_long(state, "have_weekday_relative", intervalobj->diff->have_weekday_relative); - add_assoc_long(state, "have_special_relative", intervalobj->diff->have_special_relative); - add_assoc_long(state, "civil_or_wall", intervalobj->civil_or_wall); - - return true; - } - - return false; -} - -static bool php_date_unserialize_direct_cache_state(zval *object, zval *state) -{ - if (Z_TYPE_P(state) != IS_ARRAY) { - return false; - } - - if (instanceof_function(Z_OBJCE_P(object), date_ce_date) || - instanceof_function(Z_OBJCE_P(object), date_ce_immutable)) { - php_date_obj *dateobj = Z_PHPDATE_P(object); - - return php_date_initialize_from_hash(&dateobj, Z_ARRVAL_P(state)); - } - - if (instanceof_function(Z_OBJCE_P(object), date_ce_timezone)) { - php_timezone_obj *tzobj = Z_PHPTIMEZONE_P(object); - - return php_date_timezone_initialize_from_hash(&tzobj, Z_ARRVAL_P(state)); - } - - if (instanceof_function(Z_OBJCE_P(object), date_ce_interval)) { - php_date_interval_initialize_from_hash(Z_PHPINTERVAL_P(object), Z_ARRVAL_P(state)); - - return !EG(exception); - } - - return false; -} - -const zend_opcache_static_cache_safe_direct_handlers *php_date_get_direct_cache_handlers(void) -{ - static const zend_opcache_static_cache_safe_direct_handlers handlers = { - true, - { false, NULL, NULL }, - php_date_copy_direct_cache_state, - NULL, - php_date_serialize_direct_cache_state, - php_date_unserialize_direct_cache_state - }; - - return &handlers; -} - static HashTable *date_object_get_properties_interval(zend_object *object) /* {{{ */ { HashTable *props; diff --git a/ext/date/php_date.h b/ext/date/php_date.h index 66a6706d91e1..8d7dcd2a5ec1 100644 --- a/ext/date/php_date.h +++ b/ext/date/php_date.h @@ -18,8 +18,6 @@ #include "lib/timelib.h" #include "Zend/zend_hash.h" -#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ - /* Same as SIZEOF_ZEND_LONG but using TIMELIB_LONG_MAX/MIN */ #if TIMELIB_LONG_MAX == INT32_MAX # define PHP_DATE_SIZEOF_LONG 4 @@ -163,6 +161,4 @@ PHPAPI bool php_date_initialize(php_date_obj *dateobj, const char *time_str, siz PHPAPI void php_date_initialize_from_ts_long(php_date_obj *dateobj, zend_long sec, int usec); PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts); -const zend_opcache_static_cache_safe_direct_handlers *php_date_get_direct_cache_handlers(void); - #endif /* PHP_DATE_H */ diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index a3f8de655cea..50fb9fa31b8e 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -347,7 +347,6 @@ PHP_NEW_EXTENSION([opcache], m4_normalize([ [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $JIT_CFLAGS],, [yes]) -PHP_ADD_EXTENSION_DEP(opcache, date) PHP_ADD_EXTENSION_DEP(opcache, pcre) if test "$php_cv_shm_ipc" != "yes" && test "$php_cv_shm_mmap_posix" != "yes" && test "$php_cv_shm_mmap_anon" != "yes"; then diff --git a/ext/opcache/config.w32 b/ext/opcache/config.w32 index 55e3bc9f3e5e..e57b9c23fe7a 100644 --- a/ext/opcache/config.w32 +++ b/ext/opcache/config.w32 @@ -21,7 +21,6 @@ ZEND_EXTENSION('opcache', "\ zend_shared_alloc.c \ shared_alloc_win32.c", false, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); -ADD_EXTENSION_DEP('opcache', 'date'); ADD_EXTENSION_DEP('opcache', 'hash'); ADD_EXTENSION_DEP('opcache', 'pcre'); diff --git a/ext/opcache/opcache.stub.php b/ext/opcache/opcache.stub.php index ea7bb069d9e4..962eb1611757 100644 --- a/ext/opcache/opcache.stub.php +++ b/ext/opcache/opcache.stub.php @@ -83,11 +83,6 @@ final class VolatileStatic public function __construct(int $ttl = 0, CacheStrategy $strategy = CacheStrategy::Immediate) {} } -#[\Attribute(1)] /* TARGET_CLASS */ -final class __DirectCacheSafe -{ -} - function volatile_store(string $key, null|bool|int|float|string|array|object $value, int $ttl = 0): bool {} function volatile_store_array(array $values, int $ttl = 0): bool {} diff --git a/ext/opcache/opcache_arginfo.h b/ext/opcache/opcache_arginfo.h index 939ac39c2d1e..de23efa4d492 100644 --- a/ext/opcache/opcache_arginfo.h +++ b/ext/opcache/opcache_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit opcache.stub.php instead. - * Stub hash: 4632e9e17247948c4af8fb0c301112d78bc30c32 */ + * Stub hash: cc89c045c31ebd7d8db0e9c0a12c136451472cc5 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_reset, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -334,18 +334,3 @@ static zend_class_entry *register_class_OPcache_VolatileStatic(void) return class_entry; } - -static zend_class_entry *register_class_OPcache___DirectCacheSafe(void) -{ - zend_class_entry ce, *class_entry; - - INIT_NS_CLASS_ENTRY(ce, "OPcache", "__DirectCacheSafe", NULL); - class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); - - zend_string *attribute_name_Attribute_class_OPcache___DirectCacheSafe_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); - zend_attribute *attribute_Attribute_class_OPcache___DirectCacheSafe_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_OPcache___DirectCacheSafe_0, 1); - zend_string_release_ex(attribute_name_Attribute_class_OPcache___DirectCacheSafe_0, true); - ZVAL_LONG(&attribute_Attribute_class_OPcache___DirectCacheSafe_0->args[0].value, 1); - - return class_entry; -} diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_direct_cache_safe_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_direct_cache_safe_001.phpt index 424e6cdfc7d1..5e26958f1842 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_direct_cache_safe_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_direct_cache_safe_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache __DirectCacheSafe subclasses survive requests, safe serializer overrides stay direct, and wakeup hooks fallback +FPM: OPcache direct cache subclasses survive requests, safe serializer overrides stay direct, and wakeup hooks fallback --EXTENSIONS-- opcache spl diff --git a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_nested_shared_object_copy_on_write_001.phpt b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_nested_shared_object_copy_on_write_001.phpt index 38aaee74e677..08aefa27cef0 100644 --- a/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_nested_shared_object_copy_on_write_001.phpt +++ b/ext/opcache/tests/fpm/static_cache_fpm_volatile_cache_nested_shared_object_copy_on_write_001.phpt @@ -1,5 +1,5 @@ --TEST-- -FPM: OPcache volatile cache keeps nested userland objects shared until mutation with __DirectCacheSafe properties across requests +FPM: OPcache volatile cache keeps nested userland objects shared until mutation with direct cache properties across requests --SKIPIF-- --FILE-- diff --git a/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt b/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt index f9ea05325002..fbb0f3f1e431 100644 --- a/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt +++ b/ext/opcache/tests/static_cache_direct_cache_safe_disabled_without_static_cache_memory_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache __DirectCacheSafe does not touch internal classes when static cache memory is disabled +OPcache direct cache marker is not exposed when static cache memory is disabled --EXTENSIONS-- opcache spl @@ -11,13 +11,16 @@ opcache.static_cache.pinned_size_mb=0 --FILE-- getAttributes(OPcache\__DirectCacheSafe::class))); -var_dump(count((new ReflectionClass(DateTimeImmutable::class))->getAttributes(OPcache\__DirectCacheSafe::class))); -var_dump(count((new ReflectionClass(SplFixedArray::class))->getAttributes(OPcache\__DirectCacheSafe::class))); -var_dump(count((new ReflectionClass(ArrayObject::class))->getAttributes(OPcache\__DirectCacheSafe::class))); +$marker = 'OPcache\\__DirectCacheSafe'; +var_dump(class_exists($marker, false)); +var_dump(count((new ReflectionClass(DateTime::class))->getAttributes($marker))); +var_dump(count((new ReflectionClass(DateTimeImmutable::class))->getAttributes($marker))); +var_dump(count((new ReflectionClass(SplFixedArray::class))->getAttributes($marker))); +var_dump(count((new ReflectionClass(ArrayObject::class))->getAttributes($marker))); ?> --EXPECT-- +bool(false) int(0) int(0) int(0) diff --git a/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt index 83b9b334b942..6c63c497431b 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_request_local_safe_direct_slot_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache explicit fetch request-local slots clone __DirectCacheSafe objects +OPcache explicit fetch request-local slots clone direct cache objects --EXTENSIONS-- opcache spl diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_001.phpt index be70c3dc6095..21e1691c4b75 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache __DirectCacheSafe uses a direct DateTime path for safe subclasses and keeps fallback for wakeup hooks +OPcache direct cache handlers use a direct DateTime path for safe subclasses and keep fallback for wakeup hooks --EXTENSIONS-- opcache --INI-- @@ -9,9 +9,6 @@ opcache.static_cache.volatile_size_mb=32 --FILE-- getAttributes(OPcache\__DirectCacheSafe::class))); -var_dump(count((new ReflectionClass(DateTimeImmutable::class))->getAttributes(OPcache\__DirectCacheSafe::class))); - $immutable = new DateTimeImmutable('2026-06-15 11:00:00.111111', new DateTimeZone('UTC')); var_dump(OPcache\volatile_store('immutable_datetime', $immutable)); @@ -160,8 +157,6 @@ var_dump(WakefulSerializedDateTime::$unserializeCount); ?> --EXPECT-- -int(1) -int(1) bool(true) bool(true) string(30) "2026-06-15 11:00:00.111111 UTC" diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_002.phpt index 65713eb13cf2..cc4d512e5ab1 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_002.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_002.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache __DirectCacheSafe covers DateTimeZone and DateInterval direct paths +OPcache direct cache handlers cover DateTimeZone and DateInterval direct paths --EXTENSIONS-- opcache --INI-- @@ -9,9 +9,6 @@ opcache.static_cache.volatile_size_mb=32 --FILE-- getAttributes(OPcache\__DirectCacheSafe::class))); -var_dump(count((new ReflectionClass(DateInterval::class))->getAttributes(OPcache\__DirectCacheSafe::class))); - class TaggedTimeZone extends DateTimeZone { private string $label; @@ -90,8 +87,6 @@ var_dump($relativeIntervalCopy->format('%d %h')); ?> --EXPECT-- -int(1) -int(1) bool(true) bool(true) string(12) "Europe/Paris" diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_003.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_003.phpt index 58015f64cdcb..73b371bed16e 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_003.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_003.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache __DirectCacheSafe covers SPL direct paths +OPcache direct cache handlers cover SPL direct paths --EXTENSIONS-- opcache spl @@ -10,11 +10,6 @@ opcache.static_cache.volatile_size_mb=32 --FILE-- getAttributes(OPcache\__DirectCacheSafe::class))); -var_dump(count((new ReflectionClass(ArrayObject::class))->getAttributes(OPcache\__DirectCacheSafe::class))); -var_dump(count((new ReflectionClass(ArrayIterator::class))->getAttributes(OPcache\__DirectCacheSafe::class))); -var_dump(count((new ReflectionClass(RecursiveArrayIterator::class))->getAttributes(OPcache\__DirectCacheSafe::class))); - class TaggedFixedArray extends SplFixedArray { private string $tag; @@ -169,10 +164,6 @@ var_dump($customCopy['x']); ?> --EXPECT-- -int(1) -int(1) -int(1) -int(1) bool(true) bool(true) int(3) diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_004.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_004.phpt index 3f61fac97ea7..97f8a738ccff 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_004.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_004.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache __DirectCacheSafe covers SPL edge branches +OPcache direct cache handlers cover SPL edge branches --EXTENSIONS-- opcache spl diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_fork_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_fork_001.phpt index 82d08922509a..984b0d6f0d56 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_fork_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_fork_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache __DirectCacheSafe subclasses survive forked fetch and safe serializer overrides stay direct +OPcache direct cache subclasses survive forked fetch and safe serializer overrides stay direct --EXTENSIONS-- opcache pcntl diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt index 2791756018df..ef658ef2de62 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache __DirectCacheSafe rejects unstorable values in SPL subclass state +OPcache direct cache handlers reject unstorable values in SPL subclass state --EXTENSIONS-- opcache spl diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc deleted file mode 100644 index 2cc073a96833..000000000000 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc +++ /dev/null @@ -1,6 +0,0 @@ -isInternal()); -var_dump($marker->isFinal()); - -var_dump(count((new ReflectionClass(DateTimeImmutable::class))->getAttributes(OPcache\__DirectCacheSafe::class))); +$marker = 'OPcache\\__DirectCacheSafe'; +var_dump(class_exists($marker, false)); +var_dump(in_array($marker, get_declared_classes(), true)); +var_dump(count((new ReflectionClass(DateTimeImmutable::class))->getAttributes($marker))); $value = new DateTimeImmutable('2026-01-02 03:04:05', new DateTimeZone('UTC')); var_dump(OPcache\volatile_store('hidden_safe_direct_datetime', $value)); var_dump(OPcache\volatile_fetch('hidden_safe_direct_datetime')->format('Y-m-d H:i:s e')); -require __DIR__ . '/static_cache_volatile_cache_direct_cache_safe_visibility_001.inc'; - ?> ---EXPECTF-- -bool(true) -bool(true) -bool(true) -bool(true) -int(1) +--EXPECT-- +bool(false) +bool(false) +int(0) bool(true) string(23) "2026-01-02 03:04:05 UTC" - -Fatal error: Only internal classes can be marked with #[OPcache\__DirectCacheSafe] in %sstatic_cache_volatile_cache_direct_cache_safe_visibility_001.inc on line %d diff --git a/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_002.phpt index 05d1c3df1e64..becc0141182f 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_002.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_materialization_fork_002.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache volatile cache should isolate nested post-mutation fetches even with __DirectCacheSafe properties +OPcache volatile cache should isolate nested post-mutation fetches with direct cache properties --EXTENSIONS-- opcache pcntl diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt index 1c5f3de00261..3b0204990805 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache volatile cache validates __DirectCacheSafe and copy-on-mutate behavior across ZTS threads +OPcache volatile cache validates direct cache handlers and copy-on-mutate behavior across ZTS threads --EXTENSIONS-- opcache --SKIPIF-- diff --git a/ext/opcache/zend_opcache_serializer.h b/ext/opcache/zend_opcache_serializer.h index 2b05285f3751..832aa58900ce 100644 --- a/ext/opcache/zend_opcache_serializer.h +++ b/ext/opcache/zend_opcache_serializer.h @@ -19,7 +19,6 @@ #include -#include "Zend/zend_attributes.h" #include "Zend/zend_closures.h" #define ZEND_OPCACHE_SERIALIZER_ALIGN8(size) (((size) + 7UL) & ~7UL) @@ -45,8 +44,6 @@ #define ZEND_OPCACHE_SERIALIZER_ARRAY_MAP 0x00 #define ZEND_OPCACHE_SERIALIZER_ARRAY_PACKED 0x01 -#define ZEND_OPCACHE_STATIC_CACHE_DIRECT_CACHE_SAFE_ATTRIBUTE "opcache\\__directcachesafe" - #define ZEND_OPCACHE_SERIALIZER_OBJ_SERIALIZE 0x01 #define ZEND_OPCACHE_SERIALIZER_OBJ_PROPS 0x02 #define ZEND_OPCACHE_SERIALIZER_OBJ_PHP_SER 0x04 @@ -404,13 +401,6 @@ static zend_always_inline zval *zend_opcache_serializer_find_sleep_property(Hash return NULL; } -static zend_always_inline bool zend_opcache_serializer_safe_direct_cache_has_attribute(const HashTable *attributes) -{ - return attributes != NULL && - zend_get_attribute_str(attributes, ZEND_STRL(ZEND_OPCACHE_STATIC_CACHE_DIRECT_CACHE_SAFE_ATTRIBUTE)) != NULL - ; -} - static zend_always_inline zend_class_entry *zend_opcache_serializer_find_safe_direct_cache_base(zend_class_entry *ce) { zend_class_entry *base_ce = NULL; diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index 30d39c00e8d0..dc286846c1f6 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -27,9 +27,6 @@ #include "zend_static_cache.h" #include "zend_smart_str.h" -#include "ext/date/php_date.h" -#include "ext/spl/spl_array.h" -#include "ext/spl/spl_fixedarray.h" #include "ext/standard/php_var.h" #include "SAPI.h" @@ -47,7 +44,6 @@ zend_class_entry *zend_opcache_static_cache_info_ce; static zend_class_entry *zend_opcache_static_cache_pinned_attribute_ce; static zend_class_entry *zend_opcache_static_cache_volatile_static_attribute_ce; -static zend_class_entry *zend_opcache_static_cache_safe_direct_attribute_ce; ZEND_METHOD(OPcache_StaticCacheInfo, __construct) { @@ -69,7 +65,6 @@ zend_opcache_static_cache_context zend_opcache_static_cache_pinned_context_state false, true }; -bool zend_opcache_static_cache_safe_direct_classes_marked = false; bool zend_opcache_static_cache_subsystem_disabled = false; const char *zend_opcache_static_cache_subsystem_failure_reason = NULL; @@ -315,7 +310,6 @@ void zend_opcache_static_cache_safe_direct_register_class( zend_opcache_static_cache_safe_direct_handlers handlers_copy; if (ce == NULL || - zend_opcache_static_cache_safe_direct_attribute_ce == NULL || handlers == NULL || handlers->copy == NULL || handlers->state_serialize == NULL || @@ -324,10 +318,6 @@ void zend_opcache_static_cache_safe_direct_register_class( return; } - if (!zend_opcache_serializer_safe_direct_cache_has_attribute(ce->attributes)) { - zend_add_class_attribute(ce, zend_opcache_static_cache_safe_direct_attribute_ce->name, 0); - } - zend_opcache_static_cache_safe_direct_handlers_init(); handlers_copy = *handlers; zend_hash_index_update_mem( @@ -367,115 +357,6 @@ static const zend_opcache_static_cache_safe_direct_handlers *zend_opcache_static return NULL; } -static bool zend_opcache_static_cache_safe_direct_serializer_path_serialize( - const zval *object, - zval *state) -{ - const zend_opcache_static_cache_safe_direct_handlers *handlers = - zend_opcache_static_cache_safe_direct_find_handlers(Z_OBJCE_P(object), NULL) - ; - - ZVAL_UNDEF(state); - if (handlers == NULL || handlers->serializer_path.serialize == NULL) { - return false; - } - - zend_call_known_instance_method_with_0_params(handlers->serializer_path.serialize, Z_OBJ_P(object), state); - if (EG(exception) || Z_TYPE_P(state) != IS_ARRAY) { - if (Z_TYPE_P(state) != IS_UNDEF) { - zval_ptr_dtor(state); - ZVAL_UNDEF(state); - } - - return false; - } - - return true; -} - -static bool zend_opcache_static_cache_safe_direct_serializer_path_unserialize( - zval *object, - zval *state) -{ - const zend_opcache_static_cache_safe_direct_handlers *handlers = - zend_opcache_static_cache_safe_direct_find_handlers(Z_OBJCE_P(object), NULL) - ; - - if (handlers == NULL || handlers->serializer_path.unserialize == NULL || Z_TYPE_P(state) != IS_ARRAY) { - return false; - } - - zend_call_known_instance_method_with_1_params(handlers->serializer_path.unserialize, Z_OBJ_P(object), NULL, state); - - return !EG(exception); -} - -static bool zend_opcache_static_cache_safe_direct_serializer_path_copy( - void *context, - zend_object *old_object, - zend_object *new_object, - zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value) -{ - zval old_zv, new_zv, state_zv, cloned_state_zv; - bool result = false; - - if (clone_value == NULL) { - return false; - } - - ZVAL_OBJ(&old_zv, old_object); - ZVAL_OBJ(&new_zv, new_object); - ZVAL_UNDEF(&state_zv); - ZVAL_UNDEF(&cloned_state_zv); - - if (!zend_opcache_static_cache_safe_direct_serializer_path_serialize(&old_zv, &state_zv)) { - goto cleanup; - } - - if (!clone_value(context, &cloned_state_zv, &state_zv) || - Z_TYPE(cloned_state_zv) != IS_ARRAY) { - goto cleanup; - } - - result = zend_opcache_static_cache_safe_direct_serializer_path_unserialize( - &new_zv, - &cloned_state_zv - ); - -cleanup: - if (Z_TYPE(cloned_state_zv) != IS_UNDEF) { - zval_ptr_dtor(&cloned_state_zv); - } - if (Z_TYPE(state_zv) != IS_UNDEF) { - zval_ptr_dtor(&state_zv); - } - - return result && !EG(exception); -} - -static bool zend_opcache_static_cache_safe_direct_serializer_path_has_unstorable( - void *context, - const zval *value, - zend_opcache_static_cache_safe_direct_value_has_unstorable_func_t value_has_unstorable) -{ - zval state_zv; - bool result; - - if (value_has_unstorable == NULL) { - return false; - } - - ZVAL_UNDEF(&state_zv); - if (!zend_opcache_static_cache_safe_direct_serializer_path_serialize(value, &state_zv)) { - return true; - } - - result = value_has_unstorable(context, &state_zv); - zval_ptr_dtor(&state_zv); - - return result; -} - zend_opcache_static_cache_safe_direct_state_copy_func_t zend_opcache_static_cache_safe_direct_copy_func( zend_class_entry *ce, zend_class_entry **base_ce_ptr) @@ -544,22 +425,6 @@ static zend_always_inline zend_string *zend_opcache_static_cache_validate_volati return NULL; } -static zend_always_inline zend_string *zend_opcache_static_cache_validate_direct_cache_safe_attribute( - zend_attribute *attr ZEND_ATTRIBUTE_UNUSED, - uint32_t target, - zend_class_entry *scope) -{ - if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { - return ZSTR_INIT_LITERAL("Only classes can be marked with #[OPcache\\__DirectCacheSafe]", 0); - } - - if (scope == NULL || scope->type != ZEND_INTERNAL_CLASS) { - return ZSTR_INIT_LITERAL("Only internal classes can be marked with #[OPcache\\__DirectCacheSafe]", 0); - } - - return NULL; -} - static zend_always_inline void zend_opcache_static_cache_register_classes(void) { zend_internal_attribute *attribute; @@ -575,90 +440,16 @@ static zend_always_inline void zend_opcache_static_cache_register_classes(void) zend_opcache_static_cache_volatile_static_attribute_ce = register_class_OPcache_VolatileStatic(); attribute = zend_mark_internal_attribute(zend_opcache_static_cache_volatile_static_attribute_ce); attribute->validator = zend_opcache_static_cache_validate_volatile_static_attribute; - zend_opcache_static_cache_safe_direct_attribute_ce = register_class_OPcache___DirectCacheSafe(); - attribute = zend_mark_internal_attribute(zend_opcache_static_cache_safe_direct_attribute_ce); - attribute->validator = zend_opcache_static_cache_validate_direct_cache_safe_attribute; zend_opcache_static_cache_exception_ce = register_class_OPcache_StaticCacheException(zend_ce_exception); } -static zend_always_inline void zend_opcache_static_cache_safe_direct_register_serializer_class( - zend_class_entry *ce, - bool allows_custom_serializers) +static zend_always_inline void zend_opcache_static_cache_reset_class_entries(void) { - zend_opcache_static_cache_safe_direct_handlers handlers; - - if (ce == NULL || ce->__serialize == NULL || ce->__unserialize == NULL) { - return; - } - - handlers.allows_custom_serializers = allows_custom_serializers; - handlers.serializer_path.state_includes_properties = true; - handlers.serializer_path.serialize = ce->__serialize; - handlers.serializer_path.unserialize = ce->__unserialize; - handlers.copy = zend_opcache_static_cache_safe_direct_serializer_path_copy; - handlers.state_has_unstorable = zend_opcache_static_cache_safe_direct_serializer_path_has_unstorable; - handlers.state_serialize = zend_opcache_static_cache_safe_direct_serializer_path_serialize; - handlers.state_unserialize = zend_opcache_static_cache_safe_direct_serializer_path_unserialize; - - zend_opcache_static_cache_safe_direct_register_class(ce, &handlers); -} - -static zend_always_inline void zend_opcache_static_cache_safe_direct_register_internal_classes(void) -{ - zend_class_entry *date_ce, *immutable_ce, *timezone_ce, *interval_ce, *fixedarray_ce, *arrayobject_ce, *arrayiterator_ce, *recursive_arrayiterator_ce; - const zend_opcache_static_cache_safe_direct_handlers *date_handlers, *spl_array_handlers, *spl_fixedarray_handlers; - - if (zend_opcache_static_cache_safe_direct_classes_marked) { - return; - } - - date_ce = php_date_get_date_ce(); - immutable_ce = php_date_get_immutable_ce(); - timezone_ce = php_date_get_timezone_ce(); - interval_ce = php_date_get_interval_ce(); - fixedarray_ce = spl_ce_SplFixedArray; - arrayobject_ce = spl_ce_ArrayObject; - arrayiterator_ce = spl_ce_ArrayIterator; - recursive_arrayiterator_ce = spl_ce_RecursiveArrayIterator; - if (date_ce == NULL || immutable_ce == NULL || timezone_ce == NULL || interval_ce == NULL || - fixedarray_ce == NULL || arrayobject_ce == NULL || arrayiterator_ce == NULL || - recursive_arrayiterator_ce == NULL - ) { - return; - } - - date_handlers = php_date_get_direct_cache_handlers(); - spl_fixedarray_handlers = spl_fixedarray_object_get_direct_cache_handlers(); - spl_array_handlers = spl_array_object_get_direct_cache_handlers(); - if (date_handlers != NULL) { - zend_opcache_static_cache_safe_direct_register_class(date_ce, date_handlers); - zend_opcache_static_cache_safe_direct_register_class(immutable_ce, date_handlers); - zend_opcache_static_cache_safe_direct_register_class(timezone_ce, date_handlers); - zend_opcache_static_cache_safe_direct_register_class(interval_ce, date_handlers); - } else { - zend_opcache_static_cache_safe_direct_register_serializer_class(date_ce, true); - zend_opcache_static_cache_safe_direct_register_serializer_class(immutable_ce, true); - zend_opcache_static_cache_safe_direct_register_serializer_class(timezone_ce, true); - zend_opcache_static_cache_safe_direct_register_serializer_class(interval_ce, true); - } - - if (spl_fixedarray_handlers != NULL) { - zend_opcache_static_cache_safe_direct_register_class(fixedarray_ce, spl_fixedarray_handlers); - } else { - zend_opcache_static_cache_safe_direct_register_serializer_class(fixedarray_ce, false); - } - - if (spl_array_handlers != NULL) { - zend_opcache_static_cache_safe_direct_register_class(arrayobject_ce, spl_array_handlers); - zend_opcache_static_cache_safe_direct_register_class(arrayiterator_ce, spl_array_handlers); - zend_opcache_static_cache_safe_direct_register_class(recursive_arrayiterator_ce, spl_array_handlers); - } else { - zend_opcache_static_cache_safe_direct_register_serializer_class(arrayobject_ce, false); - zend_opcache_static_cache_safe_direct_register_serializer_class(arrayiterator_ce, false); - zend_opcache_static_cache_safe_direct_register_serializer_class(recursive_arrayiterator_ce, false); - } - - zend_opcache_static_cache_safe_direct_classes_marked = true; + zend_opcache_static_cache_exception_ce = NULL; + zend_opcache_static_cache_strategy_ce = NULL; + zend_opcache_static_cache_info_ce = NULL; + zend_opcache_static_cache_pinned_attribute_ce = NULL; + zend_opcache_static_cache_volatile_static_attribute_ce = NULL; } static zend_always_inline void zend_opcache_static_cache_invalidate_script_context(zend_opcache_static_cache_context *context, zend_persistent_script *persistent_script) @@ -1200,15 +991,6 @@ static void zend_opcache_static_cache_post_startup(void) } zend_opcache_static_cache_restore_context(previous_context); - - if (!zend_opcache_static_cache_subsystem_disabled && - ( - ZCG(accel_directives).static_cache_volatile_size_mb != 0 || - ZCG(accel_directives).static_cache_pinned_size_mb != 0 - ) - ) { - zend_opcache_static_cache_safe_direct_register_internal_classes(); - } } void zend_opcache_static_cache_mshutdown(void) @@ -1227,8 +1009,8 @@ void zend_opcache_static_cache_mshutdown(void) zend_opcache_static_cache_subsystem_disabled = false; zend_opcache_static_cache_subsystem_failure_reason = NULL; - zend_opcache_static_cache_safe_direct_classes_marked = false; zend_opcache_static_cache_safe_direct_handlers_destroy(); + zend_opcache_static_cache_reset_class_entries(); zend_accel_register_static_cache_handlers(NULL); } @@ -1305,7 +1087,6 @@ zend_result zend_opcache_static_cache_minit(void) zend_opcache_static_cache_subsystem_disabled = false; zend_opcache_static_cache_subsystem_failure_reason = NULL; - zend_opcache_static_cache_safe_direct_classes_marked = false; zend_opcache_static_cache_register_classes(); zend_opcache_static_cache_safe_direct_handlers_init(); diff --git a/ext/opcache/zend_static_cache.h b/ext/opcache/zend_static_cache.h index 4ef30c9ab40e..78fb81b8a759 100644 --- a/ext/opcache/zend_static_cache.h +++ b/ext/opcache/zend_static_cache.h @@ -86,6 +86,10 @@ const char *zend_opcache_static_cache_volatile_failure_reason(void); bool zend_opcache_static_cache_pinned_is_enabled(void); bool zend_opcache_static_cache_pinned_is_available(void); const char *zend_opcache_static_cache_pinned_failure_reason(void); +void zend_opcache_static_cache_safe_direct_register_class( + zend_class_entry *ce, + const zend_opcache_static_cache_safe_direct_handlers *handlers +); END_EXTERN_C() diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index e0ac77e976b0..8fde2a204661 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -36,9 +36,6 @@ #include "zend_smart_str.h" #include "zend_static_cache.h" -#include "ext/date/php_date.h" -#include "ext/spl/spl_array.h" -#include "ext/spl/spl_fixedarray.h" #include "ext/standard/php_var.h" #include "SAPI.h" @@ -378,7 +375,6 @@ extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_function_statics; extern ZEND_EXT_TLS bool zend_opcache_static_cache_function_statics_initialized; extern ZEND_EXT_TLS HashTable zend_opcache_static_cache_class_blob_handles; extern ZEND_EXT_TLS bool zend_opcache_static_cache_class_blob_handles_initialized; -extern bool zend_opcache_static_cache_safe_direct_classes_marked; extern ZEND_EXT_TLS zend_opcache_static_cache_context *zend_opcache_static_cache_active_context_ptr; #ifdef ZTS extern ZEND_EXT_TLS bool zend_opcache_static_cache_zts_lock_is_write; @@ -438,9 +434,6 @@ void zend_opcache_static_cache_release_active_entry_locks(void); void zend_opcache_static_cache_release_request_entry_locks(void); void zend_opcache_static_cache_safe_direct_handlers_init(void); void zend_opcache_static_cache_safe_direct_handlers_destroy(void); -void zend_opcache_static_cache_safe_direct_register_class( - zend_class_entry *ce, - const zend_opcache_static_cache_safe_direct_handlers *handlers); zend_opcache_static_cache_safe_direct_state_copy_func_t zend_opcache_static_cache_safe_direct_copy_func( zend_class_entry *ce, zend_class_entry **base_ce_ptr); diff --git a/ext/spl/config.m4 b/ext/spl/config.m4 index f15e124ba3f5..24e7cb33b93e 100644 --- a/ext/spl/config.m4 +++ b/ext/spl/config.m4 @@ -24,6 +24,7 @@ PHP_INSTALL_HEADERS([ext/spl], m4_normalize([ spl_iterators.h spl_observer.h ])) +PHP_ADD_EXTENSION_DEP(spl, opcache) PHP_ADD_EXTENSION_DEP(spl, pcre, true) PHP_ADD_EXTENSION_DEP(spl, standard, true) PHP_ADD_EXTENSION_DEP(spl, json) diff --git a/ext/spl/config.w32 b/ext/spl/config.w32 index 06e87c663357..55d10fd66b0c 100644 --- a/ext/spl/config.w32 +++ b/ext/spl/config.w32 @@ -2,4 +2,5 @@ EXTENSION("spl", "php_spl.c spl_functions.c spl_iterators.c spl_array.c spl_directory.c spl_exceptions.c spl_observer.c spl_dllist.c spl_heap.c spl_fixedarray.c", false /*never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); PHP_SPL="yes"; +ADD_EXTENSION_DEP('spl', 'opcache'); PHP_INSTALL_HEADERS("ext/spl", "php_spl.h spl_array.h spl_directory.h spl_exceptions.h spl_functions.h spl_iterators.h spl_observer.h spl_dllist.h spl_heap.h spl_fixedarray.h"); diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 21cf72832d55..e8541908be58 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -19,7 +19,6 @@ #include "php.h" #include "ext/standard/php_var.h" #include "zend_smart_str.h" -#include "ext/opcache/zend_static_cache.h" #include "zend_execute.h" #include "zend_interfaces.h" #include "zend_exceptions.h" @@ -29,6 +28,7 @@ #include "spl_array_arginfo.h" #include "spl_exceptions.h" #include "spl_functions.h" /* For spl_set_private_debug_info_property() */ +#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ /* Defined later in the file */ PHPAPI zend_class_entry *spl_ce_ArrayIterator; @@ -1622,7 +1622,7 @@ static bool spl_array_object_unserialize_direct_cache_state(zval *object, zval * return !EG(exception); } -const zend_opcache_static_cache_safe_direct_handlers *spl_array_object_get_direct_cache_handlers(void) +static const zend_opcache_static_cache_safe_direct_handlers *spl_array_object_get_direct_cache_handlers(void) { static const zend_opcache_static_cache_safe_direct_handlers handlers = { false, @@ -1986,6 +1986,8 @@ PHP_METHOD(RecursiveArrayIterator, getChildren) /* {{{ PHP_MINIT_FUNCTION(spl_array) */ PHP_MINIT_FUNCTION(spl_array) { + const zend_opcache_static_cache_safe_direct_handlers *handlers; + spl_ce_ArrayObject = register_class_ArrayObject(zend_ce_aggregate, zend_ce_arrayaccess, zend_ce_serializable, zend_ce_countable); spl_ce_ArrayObject->create_object = spl_array_object_new; spl_ce_ArrayObject->default_object_handlers = &spl_handler_ArrayObject; @@ -2021,6 +2023,11 @@ PHP_MINIT_FUNCTION(spl_array) spl_ce_RecursiveArrayIterator->create_object = spl_array_object_new; spl_ce_RecursiveArrayIterator->get_iterator = spl_array_get_iterator; + handlers = spl_array_object_get_direct_cache_handlers(); + zend_opcache_static_cache_safe_direct_register_class(spl_ce_ArrayObject, handlers); + zend_opcache_static_cache_safe_direct_register_class(spl_ce_ArrayIterator, handlers); + zend_opcache_static_cache_safe_direct_register_class(spl_ce_RecursiveArrayIterator, handlers); + return SUCCESS; } /* }}} */ diff --git a/ext/spl/spl_array.h b/ext/spl/spl_array.h index 58e994b68ab7..f99bb3f6fe88 100644 --- a/ext/spl/spl_array.h +++ b/ext/spl/spl_array.h @@ -17,8 +17,6 @@ #include "php.h" -#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ - #define SPL_ARRAY_STD_PROP_LIST 0x00000001 #define SPL_ARRAY_ARRAY_AS_PROPS 0x00000002 #define SPL_ARRAY_CHILD_ARRAYS_ONLY 0x00000004 @@ -31,8 +29,6 @@ extern PHPAPI zend_class_entry *spl_ce_ArrayObject; extern PHPAPI zend_class_entry *spl_ce_ArrayIterator; extern PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator; -const zend_opcache_static_cache_safe_direct_handlers *spl_array_object_get_direct_cache_handlers(void); - PHP_MINIT_FUNCTION(spl_array); extern void spl_array_iterator_append(zval *object, zval *append_value); diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index 13ccd25093e9..936d0e853654 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -18,7 +18,6 @@ #endif #include "php.h" -#include "ext/opcache/zend_static_cache.h" #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_attributes.h" @@ -28,6 +27,7 @@ #include "spl_fixedarray.h" #include "spl_exceptions.h" #include "ext/json/php_json.h" /* For php_json_serializable_ce */ +#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ static zend_object_handlers spl_handler_SplFixedArray; PHPAPI zend_class_entry *spl_ce_SplFixedArray; @@ -794,7 +794,7 @@ static bool spl_fixedarray_object_unserialize_direct_cache_state(zval *object, z return !EG(exception); } -const zend_opcache_static_cache_safe_direct_handlers *spl_fixedarray_object_get_direct_cache_handlers(void) +static const zend_opcache_static_cache_safe_direct_handlers *spl_fixedarray_object_get_direct_cache_handlers(void) { static const zend_opcache_static_cache_safe_direct_handlers handlers = { false, @@ -1119,5 +1119,10 @@ PHP_MINIT_FUNCTION(spl_fixedarray) spl_handler_SplFixedArray.get_gc = spl_fixedarray_object_get_gc; spl_handler_SplFixedArray.free_obj = spl_fixedarray_object_free_storage; + zend_opcache_static_cache_safe_direct_register_class( + spl_ce_SplFixedArray, + spl_fixedarray_object_get_direct_cache_handlers() + ); + return SUCCESS; } diff --git a/ext/spl/spl_fixedarray.h b/ext/spl/spl_fixedarray.h index 447c1f9772e4..e0661a95d872 100644 --- a/ext/spl/spl_fixedarray.h +++ b/ext/spl/spl_fixedarray.h @@ -16,12 +16,8 @@ #ifndef SPL_FIXEDARRAY_H #define SPL_FIXEDARRAY_H -#include "ext/opcache/zend_static_cache.h" /* for opcache static cache */ - extern PHPAPI zend_class_entry *spl_ce_SplFixedArray; -const zend_opcache_static_cache_safe_direct_handlers *spl_fixedarray_object_get_direct_cache_handlers(void); - PHP_MINIT_FUNCTION(spl_fixedarray); #endif /* SPL_FIXEDARRAY_H */ From 801b1affdacd334fe251fe3d872554a7bf553f4a Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 09:20:42 +0000 Subject: [PATCH 21/29] refactor and regenerate func_infos --- Zend/Optimizer/zend_func_infos.h | 2 +- ext/opcache/zend_opcache_serializer.h | 59 +++++++----- ext/opcache/zend_static_cache_entries.c | 123 +++++++++++------------- 3 files changed, 93 insertions(+), 91 deletions(-) diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 5937d349451f..1af82f6cae66 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -289,7 +289,7 @@ static const func_info_t func_infos[] = { F1("opcache_get_status", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), F1("opcache_get_configuration", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), FN("OPcache\\volatile_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), - FN("OPcache\\persistent_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), + FN("OPcache\\pinned_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), F1("openssl_x509_parse", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_csr_get_subject", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_pkey_get_details", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), diff --git a/ext/opcache/zend_opcache_serializer.h b/ext/opcache/zend_opcache_serializer.h index 832aa58900ce..c14d8487623f 100644 --- a/ext/opcache/zend_opcache_serializer.h +++ b/ext/opcache/zend_opcache_serializer.h @@ -79,6 +79,27 @@ typedef struct _zend_opcache_serializer_rbuf_t { bool skip_decoded_value_capture; } zend_opcache_serializer_rbuf_t; +typedef struct _zend_opcache_serializer_has_unstorable_context { + HashTable *seen_arrays; + HashTable *seen_objects; +} zend_opcache_serializer_has_unstorable_context; + +static zend_always_inline bool zend_opcache_serializer_encode_zval( + zend_opcache_serializer_wbuf_t *wb, + const zval *zv +); + +static zend_always_inline bool zend_opcache_serializer_decode_zval( + zend_opcache_serializer_rbuf_t *rb, + zval *dst +); + +static bool zend_opcache_serializer_value_has_unstorable_ex( + const zval *value, + HashTable *seen_arrays, + HashTable *seen_objects +); + static zend_always_inline void zend_opcache_serializer_capture_decoded_value(zend_opcache_serializer_rbuf_t *rb, zval *value) { if (rb->capture_decoded_value != NULL) { @@ -415,8 +436,10 @@ static zend_always_inline zend_class_entry *zend_opcache_serializer_find_safe_di static zend_always_inline bool zend_opcache_serializer_class_magic_method_changed(zend_class_entry *ce, zend_class_entry *base_ce, const char *name, size_t name_len) { - zend_function *func = zend_hash_str_find_ptr(&ce->function_table, name, name_len); - zend_function *base_func = zend_hash_str_find_ptr(&base_ce->function_table, name, name_len); + zend_function *func, *base_func; + + func = zend_hash_str_find_ptr(&ce->function_table, name, name_len); + base_func = zend_hash_str_find_ptr(&base_ce->function_table, name, name_len); if (func == NULL) { return base_func != NULL; @@ -453,22 +476,13 @@ static zend_always_inline bool zend_opcache_serializer_has_safe_direct_cache_ove ; } -static bool zend_opcache_serializer_value_has_unstorable_ex( - const zval *value, - HashTable *seen_arrays, - HashTable *seen_objects); - -typedef struct _zend_opcache_serializer_has_unstorable_context { - HashTable *seen_arrays; - HashTable *seen_objects; -} zend_opcache_serializer_has_unstorable_context; - static bool zend_opcache_serializer_safe_direct_value_has_unstorable_callback( void *context, const zval *value) { zend_opcache_serializer_has_unstorable_context *has_unstorable_context = - (zend_opcache_serializer_has_unstorable_context *) context; + (zend_opcache_serializer_has_unstorable_context *) context + ; return zend_opcache_serializer_value_has_unstorable_ex( value, @@ -509,10 +523,10 @@ static bool zend_opcache_serializer_safe_direct_state_has_unstorable( HashTable *seen_arrays, HashTable *seen_objects) { - zend_class_entry *base_ce = NULL; zend_opcache_static_cache_safe_direct_state_copy_func_t copy_func; zend_opcache_static_cache_safe_direct_state_has_unstorable_func_t state_has_unstorable_func; zend_opcache_serializer_has_unstorable_context context; + zend_class_entry *base_ce = NULL; copy_func = zend_opcache_static_cache_safe_direct_copy_func(Z_OBJCE_P(value), &base_ce); if (copy_func == NULL || @@ -522,7 +536,8 @@ static bool zend_opcache_serializer_safe_direct_state_has_unstorable( } state_has_unstorable_func = - zend_opcache_static_cache_safe_direct_state_has_unstorable_func(Z_OBJCE_P(value)); + zend_opcache_static_cache_safe_direct_state_has_unstorable_func(Z_OBJCE_P(value)) + ; if (state_has_unstorable_func == NULL) { return false; } @@ -626,9 +641,6 @@ static zend_always_inline bool zend_opcache_serializer_hash_has_unstorable(const return result; } -static zend_always_inline bool zend_opcache_serializer_encode_zval(zend_opcache_serializer_wbuf_t *wb, - const zval *zv); - static zend_always_inline bool zend_opcache_serializer_encode_property_table(zend_opcache_serializer_wbuf_t *wb, const HashTable *props) { @@ -1328,9 +1340,11 @@ static zend_always_inline bool zend_opcache_serialize_ex( zend_opcache_serializer_wbuf_init(&wb, 256); ok = zend_opcache_serializer_encode_zval(&wb, value); + if (failed_unstorable != NULL) { *failed_unstorable = wb.failed_unstorable; } + zend_opcache_serializer_wbuf_destroy(&wb); if (!ok) { @@ -1409,9 +1423,6 @@ static zend_always_inline bool zend_opcache_serializer_rbuf_skip(zend_opcache_se return true; } -static zend_always_inline bool zend_opcache_serializer_decode_zval(zend_opcache_serializer_rbuf_t *rb, - zval *dst); - static inline bool zend_opcache_serializer_decode_zval_suppressed(zend_opcache_serializer_rbuf_t *rb, zval *dst) { bool result; @@ -1478,10 +1489,10 @@ static zend_always_inline bool zend_opcache_serializer_decode_object_payload_wit const unsigned char *arr_meta, *key_data; const char *key_str; zend_opcache_serializer_hdr_t arr_hdr; - zend_class_entry *base_ce = NULL; - zend_property_info *prop_info; zend_opcache_static_cache_safe_direct_state_copy_func_t copy_func; zend_opcache_static_cache_safe_direct_state_unserialize_func_t unserialize_func; + zend_class_entry *base_ce = NULL; + zend_property_info *prop_info; zval props_zv, state_zv, data_arr, val, *existing, *target, func_name, wakeup_rv; HashTable *obj_ht; @@ -1902,8 +1913,8 @@ static bool zend_opcache_serializer_decode_array(zend_opcache_serializer_rbuf_t static zend_always_inline bool zend_opcache_serializer_decode_zval(zend_opcache_serializer_rbuf_t *rb, zval *dst) { - zend_opcache_serializer_hdr_t hdr; const void *payload; + zend_opcache_serializer_hdr_t hdr; int64_t lval; double dval; bool previous_skip_decoded_value_capture, skip_decoded_value_capture; diff --git a/ext/opcache/zend_static_cache_entries.c b/ext/opcache/zend_static_cache_entries.c index 5def02a6e105..6684871770dc 100644 --- a/ext/opcache/zend_static_cache_entries.c +++ b/ext/opcache/zend_static_cache_entries.c @@ -80,6 +80,55 @@ static zend_always_inline void zend_opcache_static_cache_delete_entry_locked(zen zend_opcache_static_cache_bump_mutation_epoch_locked(header); } +static zend_always_inline void zend_opcache_static_cache_release_request_local_slot_context(HashTable **slots_ptr) +{ + if (*slots_ptr == NULL) { + return; + } + + zend_hash_destroy(*slots_ptr); + FREE_HASHTABLE(*slots_ptr); + *slots_ptr = NULL; +} + +static zend_always_inline bool zend_opcache_static_cache_is_expired(const zend_opcache_static_cache_entry *entry, uint64_t now) +{ + return entry->state == ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED && entry->expires_at != 0 && entry->expires_at <= now; +} + +static zend_always_inline bool zend_opcache_static_cache_maybe_expired(const zend_opcache_static_cache_entry *entry, uint64_t *now) +{ + if (entry->state != ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED || entry->expires_at == 0) { + return false; + } + + if (*now == 0) { + *now = (uint64_t) time(NULL); + } + + return zend_opcache_static_cache_is_expired(entry, *now); +} + +static zend_always_inline bool zend_opcache_static_cache_payload_can_fit_locked(size_t size) +{ + zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); + size_t total_size; + + if (!header || size == 0 || size > UINT32_MAX - sizeof(zend_opcache_static_cache_block)) { + return false; + } + + total_size = ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + size); + + return total_size <= UINT32_MAX && total_size <= header->data_size; +} + +static zend_always_inline void zend_opcache_static_cache_init_prepared_value(zend_opcache_static_cache_prepared_value *prepared) +{ + memset(prepared, 0, sizeof(*prepared)); + prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL; +} + static void zend_opcache_static_cache_request_local_slot_dtor(zval *slot_zv) { zend_opcache_static_cache_request_local_slot *slot = Z_PTR_P(slot_zv); @@ -231,14 +280,6 @@ static bool zend_opcache_static_cache_clone_request_local_array_ex( return true; } -static bool zend_opcache_static_cache_clone_request_local_array( - zend_opcache_static_cache_request_local_clone_context *context, - zval *dst, - zval *src) -{ - return zend_opcache_static_cache_clone_request_local_array_ex(context, dst, src, false); -} - static bool zend_opcache_static_cache_clone_request_local_reference( zend_opcache_static_cache_request_local_clone_context *context, zval *dst, @@ -383,12 +424,14 @@ static bool zend_opcache_static_cache_clone_request_local_safe_direct_object( zend_object *old_object, zend_object **new_object_ptr) { - zend_class_entry *ce = old_object->ce, *base_ce = NULL; zend_opcache_static_cache_safe_direct_state_copy_func_t copy_func; + zend_class_entry *ce, *base_ce = NULL; zend_object *new_object; zend_ulong key; zval new_zv; + ce = old_object->ce; + copy_func = zend_opcache_static_cache_safe_direct_copy_func(ce, &base_ce); if (copy_func == NULL || zend_opcache_serializer_has_safe_direct_cache_overrides(ce, base_ce)) { return false; @@ -462,7 +505,7 @@ static bool zend_opcache_static_cache_clone_request_local_value( switch (Z_TYPE_P(src)) { case IS_ARRAY: - return zend_opcache_static_cache_clone_request_local_array(context, dst, src); + return zend_opcache_static_cache_clone_request_local_array_ex(context, dst, src, false); case IS_OBJECT: if (!zend_opcache_static_cache_clone_request_local_object(context, Z_OBJ_P(src), &object)) { return false; @@ -738,35 +781,6 @@ static bool zend_opcache_static_cache_fetch_finish( return true; } -static void zend_opcache_static_cache_release_request_local_slot_context(HashTable **slots_ptr) -{ - if (*slots_ptr == NULL) { - return; - } - - zend_hash_destroy(*slots_ptr); - FREE_HASHTABLE(*slots_ptr); - *slots_ptr = NULL; -} - -static bool zend_opcache_static_cache_is_expired(const zend_opcache_static_cache_entry *entry, uint64_t now) -{ - return entry->state == ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED && entry->expires_at != 0 && entry->expires_at <= now; -} - -static bool zend_opcache_static_cache_maybe_expired(const zend_opcache_static_cache_entry *entry, uint64_t *now) -{ - if (entry->state != ZEND_OPCACHE_STATIC_CACHE_ENTRY_USED || entry->expires_at == 0) { - return false; - } - - if (*now == 0) { - *now = (uint64_t) time(NULL); - } - - return zend_opcache_static_cache_is_expired(entry, *now); -} - static bool zend_opcache_static_cache_find_slot_in_header_locked( zend_opcache_static_cache_header *header, zend_string *key, @@ -897,20 +911,6 @@ static bool zend_opcache_static_cache_expunge_expired_locked(void) return removed; } -static bool zend_opcache_static_cache_payload_can_fit_locked(size_t size) -{ - zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); - size_t total_size; - - if (!header || size == 0 || size > UINT32_MAX - sizeof(zend_opcache_static_cache_block)) { - return false; - } - - total_size = ZEND_ALIGNED_SIZE(sizeof(zend_opcache_static_cache_block) + size); - - return total_size <= UINT32_MAX && total_size <= header->data_size; -} - static void zend_opcache_static_cache_handle_store_failure(const char *failure_message, bool throw_on_failure) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); @@ -927,11 +927,6 @@ static void zend_opcache_static_cache_handle_store_failure(const char *failure_m } } -static void zend_opcache_static_cache_handle_store_failure_locked(const char *failure_message, bool throw_on_failure) -{ - zend_opcache_static_cache_handle_store_failure(failure_message, throw_on_failure); -} - static bool zend_opcache_static_cache_find_unstorable_value( zval *value, HashTable *seen_arrays, @@ -974,9 +969,11 @@ static bool zend_opcache_static_cache_find_unstorable_value( if (Z_TYPE_P(value) == IS_OBJECT) { object = Z_OBJ_P(value); key = (zend_ulong) (uintptr_t) object; + if (zend_hash_index_exists(seen_objects, key)) { return false; } + zend_hash_index_add_empty_element(seen_objects, key); if (object->ce->default_properties_count != 0) { @@ -1136,12 +1133,6 @@ static bool zend_opcache_static_cache_retry_store_after_pressure_locked( return false; } -static void zend_opcache_static_cache_init_prepared_value(zend_opcache_static_cache_prepared_value *prepared) -{ - memset(prepared, 0, sizeof(*prepared)); - prepared->value_type = ZEND_OPCACHE_STATIC_CACHE_VALUE_NULL; -} - bool zend_opcache_static_cache_clear_locked(void) { zend_opcache_static_cache_header *header = zend_opcache_static_cache_header_ptr(); @@ -1416,7 +1407,7 @@ bool zend_opcache_static_cache_store_prepared_locked( goto retry_store; } - zend_opcache_static_cache_handle_store_failure_locked("cache hash table is full", throw_on_failure); + zend_opcache_static_cache_handle_store_failure("cache hash table is full", throw_on_failure); return false; } @@ -1765,7 +1756,7 @@ bool zend_opcache_static_cache_store_prepared_locked( goto retry_store; } - zend_opcache_static_cache_handle_store_failure_locked(failure_message, throw_on_failure); + zend_opcache_static_cache_handle_store_failure(failure_message, throw_on_failure); return false; } From 2c5e2826cf50406b4e969f00a688deef78cafbf5 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 19:10:20 +0900 Subject: [PATCH 22/29] zend_atomic: fix argument order --- Zend/zend_atomic.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_atomic.h b/Zend/zend_atomic.h index 31558996c3c3..ddd8e7a759cd 100644 --- a/Zend/zend_atomic.h +++ b/Zend/zend_atomic.h @@ -104,7 +104,7 @@ static zend_always_inline int zend_atomic_int_exchange_ex(zend_atomic_int *obj, } static zend_always_inline bool zend_atomic_bool_compare_exchange_ex(zend_atomic_bool *obj, bool *expected, bool desired) { - bool prev = (bool) InterlockedCompareExchange8(&obj->value, *expected, desired); + bool prev = (bool) InterlockedCompareExchange8(&obj->value, desired, *expected); if (prev == *expected) { return true; } else { @@ -114,7 +114,7 @@ static zend_always_inline bool zend_atomic_bool_compare_exchange_ex(zend_atomic_ } static zend_always_inline bool zend_atomic_int_compare_exchange_ex(zend_atomic_int *obj, int *expected, int desired) { - int prev = (int) InterlockedCompareExchange(&obj->value, *expected, desired); + int prev = (int) InterlockedCompareExchange(&obj->value, desired, *expected); if (prev == *expected) { return true; } else { From dc845f2d7d0a37ab4bcf9dd5538c312aa52224e1 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 19:11:15 +0900 Subject: [PATCH 23/29] fix: Windows implementations --- ..._cache_zts_request_shared_graph_refs_001.c | 94 ++++--------------- .../helpers/volatile_cache_zts_threads_003.c | 76 --------------- ext/opcache/tests/php_cli_server.inc | 3 +- ...nt_static_store_failure_exception_001.phpt | 9 +- ...che_persistent_static_zts_threads_001.phpt | 29 +++++- ...che_zts_request_shared_graph_refs_001.phpt | 26 ++++- ..._cache_volatile_cache_zts_threads_001.phpt | 30 +++++- ..._cache_volatile_cache_zts_threads_002.phpt | 29 +++++- ..._cache_volatile_cache_zts_threads_003.phpt | 29 +++++- .../static_cache_windows_backend_001.phpt | 2 +- ext/opcache/zend_static_cache_storage.c | 2 + 11 files changed, 154 insertions(+), 175 deletions(-) diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c b/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c index 656383cbf0b1..240864cb7a60 100644 --- a/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c +++ b/ext/opcache/tests/helpers/volatile_cache_zts_request_shared_graph_refs_001.c @@ -23,13 +23,15 @@ #include #include -#include "Zend/zend_atomic.h" #include "Zend/zend_exceptions.h" #include "Zend/zend_execute.h" #include "Zend/zend_portability.h" #include "sapi/embed/php_embed.h" -#include "zend_static_cache_internal.h" +#ifndef ZEND_WIN32 +# include "Zend/zend_atomic.h" +# include "zend_static_cache_internal.h" +#endif #ifndef ZTS # error "This helper requires a ZTS build" @@ -37,79 +39,11 @@ #define REQUEST_COUNT 4 -typedef struct _zend_opcache_test_accel_directives { - zend_long memory_consumption; - zend_long static_cache_volatile_size_mb; - zend_long max_accelerated_files; - double max_wasted_percentage; - char *user_blacklist_filename; - zend_long force_restart_timeout; - bool use_cwd; - bool ignore_dups; - bool validate_timestamps; - bool revalidate_path; - bool save_comments; - bool record_warnings; - bool protect_memory; - bool file_override_enabled; - bool enable_cli; - bool validate_permission; -#ifndef ZEND_WIN32 - bool validate_root; -#endif - zend_ulong revalidate_freq; - zend_ulong file_update_protection; - char *error_log; -#ifdef ZEND_WIN32 - char *mmap_base; -#endif - char *memory_model; - zend_long log_verbosity_level; - zend_long optimization_level; - zend_long opt_debug_level; - zend_long max_file_size; - zend_long interned_strings_buffer; - char *restrict_api; -#ifndef ZEND_WIN32 - char *lockfile_path; -#endif - char *file_cache; - bool file_cache_read_only; - bool file_cache_only; - bool file_cache_consistency_checks; -#if ENABLE_FILE_CACHE_FALLBACK - bool file_cache_fallback; -#endif -#ifdef HAVE_HUGE_CODE_PAGES - bool huge_code_pages; -#endif - char *preload; -#ifndef ZEND_WIN32 - char *preload_user; -#endif -#ifdef ZEND_WIN32 - char *cache_id; -#endif -} zend_opcache_test_accel_directives; - -typedef struct _zend_opcache_test_globals { - bool counted; - bool enabled; - bool locked; - bool accelerator_enabled; - bool pcre_reseted; - zend_opcache_test_accel_directives accel_directives; -} zend_opcache_test_globals; - typedef struct _zend_opcache_ref_thread_ctx { int result; char message[256]; } zend_opcache_ref_thread_ctx; -extern size_t accel_globals_offset; - -#define ZEND_OPCACHE_TEST_CG(v) ZEND_TSRMG_FAST(accel_globals_offset, zend_opcache_test_globals *, v) - static const char opcache_test_ini[] = "html_errors=0\n" "implicit_flush=1\n" @@ -160,9 +94,7 @@ static const char worker_code[] = " return true;" "})()"; -static zend_opcache_test_accel_directives zend_opcache_thread_accel_directives; -static bool zend_opcache_thread_enabled; - +#ifndef ZEND_WIN32 static const zend_opcache_static_cache_shared_graph_header *zend_opcache_locate_shared_graph_header(uint32_t payload_offset) { const unsigned char *buffer; @@ -193,6 +125,7 @@ static const zend_opcache_static_cache_shared_graph_header *zend_opcache_locate_ return header; } +#endif static int zend_opcache_test_startup(int argc, char **argv) { @@ -214,9 +147,6 @@ static int zend_opcache_test_startup(int argc, char **argv) return FAILURE; } - zend_opcache_thread_accel_directives = ZEND_OPCACHE_TEST_CG(accel_directives); - zend_opcache_thread_enabled = ZEND_OPCACHE_TEST_CG(enabled); - SG(options) |= SAPI_OPTION_NO_CHDIR; return SUCCESS; } @@ -232,8 +162,6 @@ static bool zend_opcache_thread_request_startup(void) { (void) ts_resource(0); ZEND_TSRMLS_CACHE_UPDATE(); - ZEND_OPCACHE_TEST_CG(accel_directives) = zend_opcache_thread_accel_directives; - ZEND_OPCACHE_TEST_CG(enabled) = zend_opcache_thread_enabled; SG(request_info).argc = 0; SG(request_info).argv = NULL; @@ -299,6 +227,7 @@ static bool zend_opcache_run_request_code(const char *code, const char *label, b return false; } +#ifndef ZEND_WIN32 if (expect_single_shared_graph_ref && zend_opcache_static_cache_shared_graph_ref_count != 1) { snprintf( message, @@ -313,6 +242,9 @@ static bool zend_opcache_run_request_code(const char *code, const char *label, b php_request_shutdown(NULL); return false; } +#else + (void) expect_single_shared_graph_ref; +#endif if (!Z_ISUNDEF(retval)) { zval_ptr_dtor(&retval); @@ -321,6 +253,7 @@ static bool zend_opcache_run_request_code(const char *code, const char *label, b return true; } +#ifndef ZEND_WIN32 static bool zend_opcache_inspect_current_payload_state( uint32_t *value_offset_out, uint32_t *next_free_out, @@ -395,13 +328,16 @@ static bool zend_opcache_inspect_current_payload_state( zend_opcache_static_cache_restore_context(previous_context); return ok; } +#endif static void *zend_opcache_ref_thread_main(void *arg) { zend_opcache_ref_thread_ctx *ctx = (zend_opcache_ref_thread_ctx *) arg; +#ifndef ZEND_WIN32 uint32_t expected_offset = 0; uint32_t value_offset, next_free; int refcount; +#endif int iteration; ctx->result = 0; @@ -419,6 +355,7 @@ static void *zend_opcache_ref_thread_main(void *arg) break; } +#ifndef ZEND_WIN32 if (!zend_opcache_inspect_current_payload_state(&value_offset, &next_free, &refcount, ctx->message, sizeof(ctx->message))) { ctx->result = 1; break; @@ -452,6 +389,7 @@ static void *zend_opcache_ref_thread_main(void *arg) ); break; } +#endif } ts_free_thread(); diff --git a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c index b163e050fcb3..5937fcfc2500 100644 --- a/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c +++ b/ext/opcache/tests/helpers/volatile_cache_zts_threads_003.c @@ -32,70 +32,6 @@ # error "This helper requires a ZTS build" #endif -typedef struct _zend_opcache_test_accel_directives { - zend_long memory_consumption; - zend_long static_cache_volatile_size_mb; - zend_long max_accelerated_files; - double max_wasted_percentage; - char *user_blacklist_filename; - zend_long force_restart_timeout; - bool use_cwd; - bool ignore_dups; - bool validate_timestamps; - bool revalidate_path; - bool save_comments; - bool record_warnings; - bool protect_memory; - bool file_override_enabled; - bool enable_cli; - bool validate_permission; -#ifndef ZEND_WIN32 - bool validate_root; -#endif - zend_ulong revalidate_freq; - zend_ulong file_update_protection; - char *error_log; -#ifdef ZEND_WIN32 - char *mmap_base; -#endif - char *memory_model; - zend_long log_verbosity_level; - zend_long optimization_level; - zend_long opt_debug_level; - zend_long max_file_size; - zend_long interned_strings_buffer; - char *restrict_api; -#ifndef ZEND_WIN32 - char *lockfile_path; -#endif - char *file_cache; - bool file_cache_read_only; - bool file_cache_only; - bool file_cache_consistency_checks; -#if ENABLE_FILE_CACHE_FALLBACK - bool file_cache_fallback; -#endif -#ifdef HAVE_HUGE_CODE_PAGES - bool huge_code_pages; -#endif - char *preload; -#ifndef ZEND_WIN32 - char *preload_user; -#endif -#ifdef ZEND_WIN32 - char *cache_id; -#endif -} zend_opcache_test_accel_directives; - -typedef struct _zend_opcache_test_globals { - bool counted; - bool enabled; - bool locked; - bool accelerator_enabled; - bool pcre_reseted; - zend_opcache_test_accel_directives accel_directives; -} zend_opcache_test_globals; - typedef struct _zend_opcache_thread_ctx { const char *mode; const char *scenario_path; @@ -104,10 +40,6 @@ typedef struct _zend_opcache_thread_ctx { char message[256]; } zend_opcache_thread_ctx; -extern size_t accel_globals_offset; - -#define ZEND_OPCACHE_TEST_CG(v) ZEND_TSRMG_FAST(accel_globals_offset, zend_opcache_test_globals *, v) - static const char opcache_test_ini[] = "html_errors=0\n" "implicit_flush=1\n" @@ -120,9 +52,6 @@ static const char opcache_test_ini[] = "opcache.max_accelerated_files=200\n" "opcache.static_cache.volatile_size_mb=32\n\0"; -static zend_opcache_test_accel_directives zend_opcache_thread_accel_directives; -static bool zend_opcache_thread_enabled; - static char *zend_opcache_build_scenario_code(const char *mode, const char *scenario_path); static int zend_opcache_test_startup(int argc, char **argv) @@ -145,9 +74,6 @@ static int zend_opcache_test_startup(int argc, char **argv) return FAILURE; } - zend_opcache_thread_accel_directives = ZEND_OPCACHE_TEST_CG(accel_directives); - zend_opcache_thread_enabled = ZEND_OPCACHE_TEST_CG(enabled); - SG(options) |= SAPI_OPTION_NO_CHDIR; return SUCCESS; } @@ -163,8 +89,6 @@ static bool zend_opcache_thread_request_startup(void) { (void) ts_resource(0); ZEND_TSRMLS_CACHE_UPDATE(); - ZEND_OPCACHE_TEST_CG(accel_directives) = zend_opcache_thread_accel_directives; - ZEND_OPCACHE_TEST_CG(enabled) = zend_opcache_thread_enabled; SG(request_info).argc = 0; SG(request_info).argv = NULL; diff --git a/ext/opcache/tests/php_cli_server.inc b/ext/opcache/tests/php_cli_server.inc index 1324549f4c5d..7bcd2ca0f3bc 100644 --- a/ext/opcache/tests/php_cli_server.inc +++ b/ext/opcache/tests/php_cli_server.inc @@ -97,7 +97,8 @@ function php_cli_server_start($ini = "", $doc_root = null) { for ($i = 0; $i < 60; $i++) { $status = proc_get_status($handle); if (!($status && $status['running'])) { - if ($status && $status['exitcode'] !== -1 && $status['exitcode'] !== 0) { + if ($status && $status['exitcode'] !== -1 && $status['exitcode'] !== 0 + && !($status['exitcode'] === 255 && PHP_OS_FAMILY === 'Windows')) { printf("Server exited with non-zero status: %d\n", $status['exitcode']); printf("Server output:\n%s\n", file_get_contents($output_file)); } diff --git a/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt b/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt index 903c227a97ee..1cd00764361d 100644 --- a/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_store_failure_exception_001.phpt @@ -59,9 +59,12 @@ class PinnedStaticStoreFailureUnsupportedValueBox } OPcache\pinned_clear(); +$nullDevice = PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null'; dump_static_cache_exception('property-resource', function (): void { - $resource = fopen('/dev/null', 'r'); + global $nullDevice; + + $resource = fopen($nullDevice, 'r'); try { PinnedStaticStoreFailureProperty::$value = $resource; } finally { @@ -76,7 +79,9 @@ dump_static_cache_exception('property-closure', function (): void { }); dump_static_cache_exception('property-object-resource', function (): void { - $resource = fopen('/dev/null', 'r'); + global $nullDevice; + + $resource = fopen($nullDevice, 'r'); try { PinnedStaticStoreFailureProperty::$value = new PinnedStaticStoreFailureUnsupportedValueBox($resource); } finally { diff --git a/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt b/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt index 142870c217aa..751b1c6535fb 100644 --- a/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_static_zts_threads_001.phpt @@ -149,7 +149,26 @@ function resolveBuildCommand(string $buildRoot, string $variable, array $fallbac } $tokens = resolveBuildFlags($buildRoot, [$variable]); - return $tokens !== [] ? $tokens : $fallback; + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveWindowsBuildFlags(): array +{ + return [ + '/MD', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/D_WINDOWS', + '/DWINDOWS=1', + '/DZEND_WIN32=1', + '/DPHP_WIN32=1', + '/DWIN32', + '/D_MBCS', + '/D_USE_MATH_DEFINES', + '/DZTS=1', + '/DZEND_DEBUG=0', + '/DNDebug', + '/DNDEBUG', + ]; } function resolveBuildRoot(string $root): string @@ -223,19 +242,21 @@ if ($root === false) { } $buildRoot = resolveBuildRoot($root); +if (PHP_OS_FAMILY === 'Windows') { + putenv('PATH=' . $buildRoot . PATH_SEPARATOR . (getenv('PATH') ?: '')); +} $source = __DIR__ . '/helpers/pinned_static_zts_threads_001.c'; $binary = __DIR__ . '/helpers/pinned_static_zts_threads_001' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); $scenario = __DIR__ . '/helpers/pinned_static_zts_threads_001.inc'; $compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); -$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? resolveWindowsBuildFlags() : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); $extraLibs = resolveExtraLibs($buildRoot); if (PHP_OS_FAMILY === 'Windows') { $compileCommand = [ ...$compiler, '/nologo', - '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', - '/DHAVE_CONFIG_H', + ...$buildFlags, '/I' . $buildRoot, '/I' . $buildRoot . '/main', '/I' . $buildRoot . '/Zend', diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_request_shared_graph_refs_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_request_shared_graph_refs_001.phpt index 85aa31eb1617..5d2b9f6dd209 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_zts_request_shared_graph_refs_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_request_shared_graph_refs_001.phpt @@ -152,6 +152,25 @@ function resolveBuildCommand(string $buildRoot, string $variable, array $fallbac return $tokens !== [] ? $tokens : $fallback; } +function resolveWindowsBuildFlags(): array +{ + return [ + '/MD', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/D_WINDOWS', + '/DWINDOWS=1', + '/DZEND_WIN32=1', + '/DPHP_WIN32=1', + '/DWIN32', + '/D_MBCS', + '/D_USE_MATH_DEFINES', + '/DZTS=1', + '/DZEND_DEBUG=0', + '/DNDebug', + '/DNDEBUG', + ]; +} + function resolveBuildRoot(string $root): string { $buildRoot = null; @@ -223,10 +242,13 @@ if ($root === false) { } $buildRoot = resolveBuildRoot($root); +if (PHP_OS_FAMILY === 'Windows') { + putenv('PATH=' . $buildRoot . PATH_SEPARATOR . (getenv('PATH') ?: '')); +} $source = __DIR__ . '/helpers/volatile_cache_zts_request_shared_graph_refs_001.c'; $binary = __DIR__ . '/helpers/volatile_cache_zts_request_shared_graph_refs_001' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); $compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); -$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? resolveWindowsBuildFlags() : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); $extraLibs = resolveExtraLibs($buildRoot); if (PHP_OS_FAMILY === 'Windows') { @@ -234,6 +256,7 @@ if (PHP_OS_FAMILY === 'Windows') { ...$compiler, '/nologo', '/EHsc', + ...$buildFlags, '/I' . $buildRoot, '/I' . $buildRoot . '/main', '/I' . $buildRoot . '/Zend', @@ -251,6 +274,7 @@ if (PHP_OS_FAMILY === 'Windows') { '/I' . $root . '/ext/opcache', '/Fe:' . $binary, $source, + '/link', resolveWindowsEmbedLib($buildRoot), ]; } else { diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_001.phpt index 39faffc62df7..5fbaeba9ef2d 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_001.phpt @@ -149,7 +149,26 @@ function resolveBuildCommand(string $buildRoot, string $variable, array $fallbac } $tokens = resolveBuildFlags($buildRoot, [$variable]); - return $tokens !== [] ? $tokens : $fallback; + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveWindowsBuildFlags(): array +{ + return [ + '/MD', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/D_WINDOWS', + '/DWINDOWS=1', + '/DZEND_WIN32=1', + '/DPHP_WIN32=1', + '/DWIN32', + '/D_MBCS', + '/D_USE_MATH_DEFINES', + '/DZTS=1', + '/DZEND_DEBUG=0', + '/DNDebug', + '/DNDEBUG', + ]; } function resolveExtraLibs(string $buildRoot): array @@ -213,18 +232,21 @@ if ($root === false || $buildRoot === null) { throw new RuntimeException('Failed to resolve build root'); } +if (PHP_OS_FAMILY === 'Windows') { + putenv('PATH=' . $buildRoot . PATH_SEPARATOR . (getenv('PATH') ?: '')); +} + $source = __DIR__ . '/helpers/volatile_cache_zts_threads_001.c'; $binary = __DIR__ . '/helpers/volatile_cache_zts_threads_001' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); $compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); -$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? resolveWindowsBuildFlags() : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); $extraLibs = resolveExtraLibs($buildRoot); if (PHP_OS_FAMILY === 'Windows') { $compileCommand = [ ...$compiler, '/nologo', - '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', - '/DHAVE_CONFIG_H', + ...$buildFlags, '/I' . $buildRoot, '/I' . $buildRoot . '/main', '/I' . $buildRoot . '/Zend', diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt index 3b0204990805..e81dc2c91a7b 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_002.phpt @@ -149,7 +149,26 @@ function resolveBuildCommand(string $buildRoot, string $variable, array $fallbac } $tokens = resolveBuildFlags($buildRoot, [$variable]); - return $tokens !== [] ? $tokens : $fallback; + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveWindowsBuildFlags(): array +{ + return [ + '/MD', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/D_WINDOWS', + '/DWINDOWS=1', + '/DZEND_WIN32=1', + '/DPHP_WIN32=1', + '/DWIN32', + '/D_MBCS', + '/D_USE_MATH_DEFINES', + '/DZTS=1', + '/DZEND_DEBUG=0', + '/DNDebug', + '/DNDEBUG', + ]; } function resolveBuildRoot(string $root): string @@ -234,18 +253,20 @@ if ($root === false) { } $buildRoot = resolveBuildRoot($root); +if (PHP_OS_FAMILY === 'Windows') { + putenv('PATH=' . $buildRoot . PATH_SEPARATOR . (getenv('PATH') ?: '')); +} $source = __DIR__ . '/helpers/volatile_cache_zts_threads_002.c'; $binary = __DIR__ . '/helpers/volatile_cache_zts_threads_002' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); $compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); -$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? resolveWindowsBuildFlags() : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); $extraLibs = resolveExtraLibs($buildRoot); if (PHP_OS_FAMILY === 'Windows') { $compileCommand = [ ...$compiler, '/nologo', - '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', - '/DHAVE_CONFIG_H', + ...$buildFlags, '/I' . $buildRoot, '/I' . $buildRoot . '/main', '/I' . $buildRoot . '/Zend', diff --git a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_003.phpt b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_003.phpt index 74a7b18bc8eb..34ef9aeda22d 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_zts_threads_003.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_zts_threads_003.phpt @@ -149,7 +149,26 @@ function resolveBuildCommand(string $buildRoot, string $variable, array $fallbac } $tokens = resolveBuildFlags($buildRoot, [$variable]); - return $tokens !== [] ? $tokens : $fallback; + return $tokens !== [] ? $tokens : $fallback; +} + +function resolveWindowsBuildFlags(): array +{ + return [ + '/MD', + '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', + '/D_WINDOWS', + '/DWINDOWS=1', + '/DZEND_WIN32=1', + '/DPHP_WIN32=1', + '/DWIN32', + '/D_MBCS', + '/D_USE_MATH_DEFINES', + '/DZTS=1', + '/DZEND_DEBUG=0', + '/DNDebug', + '/DNDEBUG', + ]; } function resolveBuildRoot(string $root): string @@ -234,18 +253,20 @@ if ($root === false) { } $buildRoot = resolveBuildRoot($root); +if (PHP_OS_FAMILY === 'Windows') { + putenv('PATH=' . $buildRoot . PATH_SEPARATOR . (getenv('PATH') ?: '')); +} $source = __DIR__ . '/helpers/volatile_cache_zts_threads_003.c'; $binary = __DIR__ . '/helpers/volatile_cache_zts_threads_003' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : '.out'); $compiler = resolveBuildCommand($buildRoot, 'CC', PHP_OS_FAMILY === 'Windows' ? ['cl'] : ['gcc']); -$buildFlags = PHP_OS_FAMILY === 'Windows' ? [] : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); +$buildFlags = PHP_OS_FAMILY === 'Windows' ? resolveWindowsBuildFlags() : resolveBuildFlags($buildRoot, ['CPPFLAGS', 'CFLAGS_CLEAN']); $extraLibs = resolveExtraLibs($buildRoot); if (PHP_OS_FAMILY === 'Windows') { $compileCommand = [ ...$compiler, '/nologo', - '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1', - '/DHAVE_CONFIG_H', + ...$buildFlags, '/I' . $buildRoot, '/I' . $buildRoot . '/main', '/I' . $buildRoot . '/Zend', diff --git a/ext/opcache/tests/static_cache_windows_backend_001.phpt b/ext/opcache/tests/static_cache_windows_backend_001.phpt index 8323db2ef24e..939c71a9cea5 100644 --- a/ext/opcache/tests/static_cache_windows_backend_001.phpt +++ b/ext/opcache/tests/static_cache_windows_backend_001.phpt @@ -56,5 +56,5 @@ array(1) { } array(1) { ["backend"]=> - string(10) "pinned" + string(6) "pinned" } diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 04f2f512f8b8..4aee6fd2ddac 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -44,6 +44,7 @@ # if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) # define MAP_ANONYMOUS MAP_ANON # endif +#endif static zend_always_inline bool zend_opcache_static_cache_force_startup_failure(void) { @@ -160,6 +161,7 @@ static inline bool zend_opcache_static_cache_win32_set_segment( } #endif +#if defined(USE_MMAP) && !defined(ZEND_WIN32) static int zend_opcache_static_cache_mmap_create_segments( size_t requested_size, zend_shared_segment ***shared_segments_p, From cbb1b0bc2c196340ec6940f03983aac7610555f5 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 19:35:49 +0900 Subject: [PATCH 24/29] fix: MSVC build error --- Zend/zend_portability.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index d521458e9f3f..ecc5c9102e9f 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -488,7 +488,7 @@ static zend_always_inline bool zend_thread_start(zend_thread_t *thread, zend_thr { zend_thread_start_ctx *ctx; - ctx = malloc(sizeof(*ctx)); + ctx = (zend_thread_start_ctx *) malloc(sizeof(*ctx)); if (ctx == NULL) { return false; } From 69ff61fc722648a986f53bf804b3f30575c12482 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 10:49:49 +0000 Subject: [PATCH 25/29] refactor and behavior changes --- Zend/zend.h | 2 +- ext/opcache/opcache.stub.php | 4 +- ext/opcache/opcache_arginfo.h | 6 +- ...atic_cache_disabled_backends_info_001.phpt | 51 ++++++++++++ ...t_cache_lock_public_mutators_fork_001.phpt | 10 +-- ...c_cache_explicit_cache_signatures_001.phpt | 6 +- .../static_cache_startup_failure_001.phpt | 22 ++---- ext/opcache/zend_static_cache.c | 78 +++++++++++++++---- ext/opcache/zend_static_cache_storage.c | 12 ++- 9 files changed, 146 insertions(+), 45 deletions(-) create mode 100644 ext/opcache/tests/static_cache_disabled_backends_info_001.phpt diff --git a/Zend/zend.h b/Zend/zend.h index e2cd40c72fd9..acc0e43b6a9d 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -387,7 +387,7 @@ extern ZEND_API void (*zend_post_shutdown_cb)(void); extern ZEND_API void (*zend_accel_schedule_restart_hook)(int reason); /* These hooks are used by OPcache Static Cache to restore, publish, and track - * selected VolatileStatic and PersistentStatic state across requests. They remain + * selected VolatileStatic and PinnedStatic state across requests. They remain * NULL when the static-cache subsystem is not active. */ extern ZEND_API void (*zend_class_init_statics_hook)(zend_class_entry *ce); extern ZEND_API void (*zend_function_init_statics_hook)(zend_execute_data *execute_data); diff --git a/ext/opcache/opcache.stub.php b/ext/opcache/opcache.stub.php index 962eb1611757..80e74dee03c9 100644 --- a/ext/opcache/opcache.stub.php +++ b/ext/opcache/opcache.stub.php @@ -108,9 +108,9 @@ function volatile_clear(): void {} function volatile_cache_info(): StaticCacheInfo {} -function pinned_store(string $key, null|bool|int|float|string|array|object $value): void {} +function pinned_store(string $key, null|bool|int|float|string|array|object $value): bool {} -function pinned_store_array(array $values): void {} +function pinned_store_array(array $values): bool {} function pinned_fetch(string $key, null|bool|int|float|string|array|object $default = null): null|bool|int|float|string|array|object {} diff --git a/ext/opcache/opcache_arginfo.h b/ext/opcache/opcache_arginfo.h index de23efa4d492..5260a51bdc35 100644 --- a/ext/opcache/opcache_arginfo.h +++ b/ext/opcache/opcache_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit opcache.stub.php instead. - * Stub hash: cc89c045c31ebd7d8db0e9c0a12c136451472cc5 */ + * Stub hash: fb25c489fa59f1e062bec1e2f711d9e81b19db69 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_reset, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -74,12 +74,12 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_OPcache_volatile_cache_info, 0, 0, OPcache\\StaticCacheInfo, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_store, 0, 2, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_store, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_MASK(0, value, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_store_array, 0, 1, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_store_array, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0) ZEND_END_ARG_INFO() diff --git a/ext/opcache/tests/static_cache_disabled_backends_info_001.phpt b/ext/opcache/tests/static_cache_disabled_backends_info_001.phpt new file mode 100644 index 000000000000..b15ff329dced --- /dev/null +++ b/ext/opcache/tests/static_cache_disabled_backends_info_001.phpt @@ -0,0 +1,51 @@ +--TEST-- +OPcache static cache disabled backends report unavailable and store APIs return false +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=0 +opcache.static_cache.pinned_size_mb=0 +--FILE-- +enabled); + var_dump($info->available); + var_dump($info->startup_failed); + var_dump($info->backend_initialized); + var_dump($info->configured_memory); + var_dump($info->failure_reason); +} + +dump_info('volatile', OPcache\volatile_cache_info()); +dump_info('pinned', OPcache\pinned_cache_info()); + +var_dump(OPcache\volatile_store('key', 'value')); +var_dump(OPcache\volatile_store_array(['array-key' => 'value'])); +var_dump(OPcache\pinned_store('key', 'value')); +var_dump(OPcache\pinned_store_array(['array-key' => 'value'])); + +?> +--EXPECT-- +volatile +bool(false) +bool(false) +bool(false) +bool(false) +int(0) +NULL +pinned +bool(false) +bool(false) +bool(false) +bool(false) +int(0) +NULL +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/ext/opcache/tests/static_cache_explicit_cache_lock_public_mutators_fork_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_lock_public_mutators_fork_001.phpt index ce7475aed1c6..957021fe0879 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_lock_public_mutators_fork_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_lock_public_mutators_fork_001.phpt @@ -52,8 +52,7 @@ function cache_store(string $backend, string $key, mixed $value): mixed return OPcache\volatile_store($key, $value); } - OPcache\pinned_store($key, $value); - return null; + return OPcache\pinned_store($key, $value); } function cache_store_array(string $backend, array $values): mixed @@ -62,8 +61,7 @@ function cache_store_array(string $backend, array $values): mixed return OPcache\volatile_store_array($values); } - OPcache\pinned_store_array($values); - return null; + return OPcache\pinned_store_array($values); } function cache_fetch(string $backend, string $key, mixed $default = null): mixed @@ -256,11 +254,11 @@ clear values: owner,MISS pinned bool(true) store blocked: yes -store result: null +store result: true store value: child bool(true) store_array blocked: yes -store_array result: null +store_array result: true store_array value: child bool(true) delete blocked: no diff --git a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt index ff829069c341..353be31290ec 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt @@ -37,12 +37,14 @@ function describe_type(?ReflectionType $type): string foreach ([ 'OPcache\\volatile_store' => ['value'], + 'OPcache\\volatile_store_array' => [], 'OPcache\\volatile_fetch' => ['default'], 'OPcache\\volatile_fetch_array' => ['default'], 'OPcache\\volatile_lock' => ['lease'], 'OPcache\\volatile_unlock' => [], 'OPcache\\volatile_cache_info' => [], 'OPcache\\pinned_store' => ['value'], + 'OPcache\\pinned_store_array' => [], 'OPcache\\pinned_fetch' => ['default'], 'OPcache\\pinned_fetch_array' => ['default'], 'OPcache\\pinned_lock' => ['lease'], @@ -69,12 +71,14 @@ foreach ([ ?> --EXPECT-- OPcache\volatile_store $value=null|bool|int|float|string|array|object params=2/3 return=bool +OPcache\volatile_store_array params=1/2 return=bool OPcache\volatile_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object OPcache\volatile_fetch_array $default=?array params=1/2 return=?array OPcache\volatile_lock $lease=int params=1/2 return=bool OPcache\volatile_unlock params=1/1 return=bool OPcache\volatile_cache_info params=0/0 return=OPcache\StaticCacheInfo -OPcache\pinned_store $value=null|bool|int|float|string|array|object params=2/2 return=void +OPcache\pinned_store $value=null|bool|int|float|string|array|object params=2/2 return=bool +OPcache\pinned_store_array params=1/1 return=bool OPcache\pinned_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object OPcache\pinned_fetch_array $default=?array params=1/2 return=?array OPcache\pinned_lock $lease=int params=1/2 return=bool diff --git a/ext/opcache/tests/static_cache_startup_failure_001.phpt b/ext/opcache/tests/static_cache_startup_failure_001.phpt index de63893b4978..3e896b9fb49d 100644 --- a/ext/opcache/tests/static_cache_startup_failure_001.phpt +++ b/ext/opcache/tests/static_cache_startup_failure_001.phpt @@ -30,27 +30,17 @@ var_dump($volatileInfo == $status['volatile_cache']); var_dump($pinnedInfo == $status['pinned_cache']); var_dump($volatileInfo->failure_reason); var_dump($pinnedInfo->failure_reason); - -try { - OPcache\volatile_store('key', 'value'); -} catch (Throwable $e) { - echo get_class($e), ': ', $e->getMessage(), "\n"; -} - -try { - OPcache\pinned_store('key', 'value'); -} catch (Throwable $e) { - echo get_class($e), ': ', $e->getMessage(), "\n"; -} +var_dump(OPcache\volatile_store('key', 'value')); +var_dump(OPcache\pinned_store('key', 'value')); ?> --EXPECT-- -bool(false) +bool(true) bool(false) bool(true) bool(false) int(33554432) -bool(false) +bool(true) bool(false) bool(true) bool(false) @@ -59,5 +49,5 @@ bool(true) bool(true) string(42) "Unable to initialize shared memory backend" string(42) "Unable to initialize shared memory backend" -OPcache\StaticCacheException: Unable to initialize shared memory backend -OPcache\StaticCacheException: Unable to initialize shared memory backend +bool(false) +bool(false) diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index dc286846c1f6..c348e7247558 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -591,6 +591,17 @@ static zend_always_inline bool zend_opcache_static_cache_validate_available_writ return true; } +static zend_always_inline bool zend_opcache_static_cache_store_backend_available(void) +{ + if (!zend_opcache_validate_api_restriction()) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + + return false; + } + + return zend_opcache_static_cache_active_runtime()->available; +} + static zend_always_inline bool zend_opcache_static_cache_acquire_write_lock(void) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); @@ -1184,9 +1195,11 @@ ZEND_METHOD(OPcache_VolatileStatic, __construct) ZEND_FUNCTION(OPcache_volatile_store) { + zend_opcache_static_cache_context *previous_context; zend_string *key; zend_long ttl = 0; zval *value; + bool stored; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_STR(key) @@ -1203,11 +1216,24 @@ ZEND_FUNCTION(OPcache_volatile_store) RETURN_THROWS(); } - if (!zend_opcache_static_cache_parse_ttl(ttl, 3) || !zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_parse_ttl(ttl, 3)) { RETURN_THROWS(); } - if (!zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, false)) { + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + if (!zend_opcache_static_cache_store_backend_available()) { + zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; + } + + stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, false); + zend_opcache_static_cache_restore_context(previous_context); + + if (!stored) { if (EG(exception)) { RETURN_THROWS(); } @@ -1220,10 +1246,12 @@ ZEND_FUNCTION(OPcache_volatile_store) ZEND_FUNCTION(OPcache_volatile_store_array) { + zend_opcache_static_cache_context *previous_context; zend_string *key; zend_long ttl = 0; zval *value; HashTable *values; + bool stored; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY_HT(values) @@ -1231,7 +1259,7 @@ ZEND_FUNCTION(OPcache_volatile_store_array) Z_PARAM_LONG(ttl) ZEND_PARSE_PARAMETERS_END(); - if (!zend_opcache_static_cache_parse_ttl(ttl, 2) || !zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_parse_ttl(ttl, 2)) { RETURN_THROWS(); } @@ -1239,10 +1267,22 @@ ZEND_FUNCTION(OPcache_volatile_store_array) RETURN_THROWS(); } + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); + if (!zend_opcache_static_cache_store_backend_available()) { + zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; + } + ZEND_HASH_FOREACH_STR_KEY_VAL(values, key, value) { ZEND_ASSERT(key != NULL); - if (!zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, false)) { + stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, false); + if (!stored) { + zend_opcache_static_cache_restore_context(previous_context); if (EG(exception)) { RETURN_THROWS(); } @@ -1251,6 +1291,8 @@ ZEND_FUNCTION(OPcache_volatile_store_array) } } ZEND_HASH_FOREACH_END(); + zend_opcache_static_cache_restore_context(previous_context); + RETURN_TRUE; } @@ -1478,10 +1520,13 @@ ZEND_FUNCTION(OPcache_pinned_store) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_store_backend_available()) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, true); @@ -1490,6 +1535,8 @@ ZEND_FUNCTION(OPcache_pinned_store) if (!stored) { RETURN_THROWS(); } + + RETURN_TRUE; } ZEND_FUNCTION(OPcache_pinned_store_array) @@ -1498,28 +1545,31 @@ ZEND_FUNCTION(OPcache_pinned_store_array) zend_string *key; zval *value; HashTable *values; + bool stored; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_HT(values) ZEND_PARSE_PARAMETERS_END(); - previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_validate_available_write()) { - zend_opcache_static_cache_restore_context(previous_context); - + if (!zend_opcache_static_cache_validate_store_array_keys(values, 1)) { RETURN_THROWS(); } - if (!zend_opcache_static_cache_validate_store_array_keys(values, 1)) { + previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); + if (!zend_opcache_static_cache_store_backend_available()) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } ZEND_HASH_FOREACH_STR_KEY_VAL(values, key, value) { ZEND_ASSERT(key != NULL); - if (!zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, true)) { + stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, true); + if (!stored) { zend_opcache_static_cache_restore_context(previous_context); RETURN_THROWS(); @@ -1527,6 +1577,8 @@ ZEND_FUNCTION(OPcache_pinned_store_array) } ZEND_HASH_FOREACH_END(); zend_opcache_static_cache_restore_context(previous_context); + + RETURN_TRUE; } ZEND_FUNCTION(OPcache_pinned_fetch) diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 4aee6fd2ddac..55ca67cfd67a 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -1947,11 +1947,13 @@ void zend_opcache_static_cache_reset_runtime(void) runtime->enabled = runtime->configured_memory != 0; if (zend_opcache_static_cache_subsystem_disabled) { - runtime->enabled = false; runtime->available = false; - runtime->startup_failed = true; + runtime->startup_failed = runtime->enabled; runtime->backend_initialized = context->storage.initialized; - runtime->failure_reason = zend_opcache_static_cache_subsystem_failure_reason; + runtime->failure_reason = runtime->enabled + ? zend_opcache_static_cache_subsystem_failure_reason + : NULL + ; } } @@ -2195,6 +2197,10 @@ void zend_opcache_static_cache_ensure_ready(void) zend_opcache_static_cache_reset_runtime(); runtime = zend_opcache_static_cache_context_runtime(context); + if (zend_opcache_static_cache_subsystem_disabled) { + return; + } + if (!runtime->enabled) { return; } From 3bff37f7cb80bbbdaf70490df967f55d44d6e694 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 12:16:42 +0000 Subject: [PATCH 26/29] fix API --- ext/opcache/opcache.stub.php | 48 +- ext/opcache/opcache_arginfo.h | 24 +- ...atic_cache_disabled_backends_info_001.phpt | 129 ++++- ...che_fragmentation_relocation_skip_001.phpt | 5 +- ...c_cache_explicit_cache_signatures_001.phpt | 73 ++- ...ent_cache_atomic_increment_create_001.phpt | 2 +- ...rsistent_cache_atomic_non_integer_001.phpt | 9 +- ...e_persistent_cache_store_overflow_001.phpt | 7 +- ...ache_direct_cache_safe_unstorable_001.phpt | 4 +- ...he_volatile_cache_storable_values_001.phpt | 20 +- ext/opcache/zend_static_cache.c | 510 +++++++++++++----- ext/opcache/zend_static_cache_entries.c | 61 ++- ext/opcache/zend_static_cache_internal.h | 17 +- ext/opcache/zend_static_cache_statics.c | 12 +- ext/opcache/zend_static_cache_storage.c | 10 +- 15 files changed, 656 insertions(+), 275 deletions(-) diff --git a/ext/opcache/opcache.stub.php b/ext/opcache/opcache.stub.php index 80e74dee03c9..9425f936252b 100644 --- a/ext/opcache/opcache.stub.php +++ b/ext/opcache/opcache.stub.php @@ -83,57 +83,57 @@ final class VolatileStatic public function __construct(int $ttl = 0, CacheStrategy $strategy = CacheStrategy::Immediate) {} } -function volatile_store(string $key, null|bool|int|float|string|array|object $value, int $ttl = 0): bool {} +function volatile_store(string $key, null|bool|int|float|string|array|object $value, int $ttl = 0, bool $throw_on_error = false): bool {} -function volatile_store_array(array $values, int $ttl = 0): bool {} +function volatile_store_array(array $values, int $ttl = 0, bool $throw_on_error = false): bool {} -function volatile_fetch(string $key, null|bool|int|float|string|array|object $default = null): null|bool|int|float|string|array|object {} +function volatile_fetch(string $key, null|bool|int|float|string|array|object $default = null, bool $throw_on_error = false): null|bool|int|float|string|array|object {} /** - * @return array|null + * @return array|false */ -function volatile_fetch_array(array $keys, ?array $default = null): ?array {} +function volatile_fetch_array(array $keys, ?array $default = null, bool $throw_on_error = false): array|false {} -function volatile_exists(string $key): bool {} +function volatile_exists(string $key, bool $throw_on_error = false): bool {} -function volatile_lock(string $key, int $lease = 0): bool {} +function volatile_lock(string $key, int $lease = 0, bool $throw_on_error = false): bool {} -function volatile_unlock(string $key): bool {} +function volatile_unlock(string $key, bool $throw_on_error = false): bool {} -function volatile_delete(string $key_or_class): void {} +function volatile_delete(string $key_or_class, bool $throw_on_error = false): bool {} -function volatile_delete_array(array $keys): void {} +function volatile_delete_array(array $keys, bool $throw_on_error = false): bool {} -function volatile_clear(): void {} +function volatile_clear(bool $throw_on_error = false): bool {} function volatile_cache_info(): StaticCacheInfo {} -function pinned_store(string $key, null|bool|int|float|string|array|object $value): bool {} +function pinned_store(string $key, null|bool|int|float|string|array|object $value, bool $throw_on_error = false): bool {} -function pinned_store_array(array $values): bool {} +function pinned_store_array(array $values, bool $throw_on_error = false): bool {} -function pinned_fetch(string $key, null|bool|int|float|string|array|object $default = null): null|bool|int|float|string|array|object {} +function pinned_fetch(string $key, null|bool|int|float|string|array|object $default = null, bool $throw_on_error = false): null|bool|int|float|string|array|object {} /** - * @return array|null + * @return array|false */ -function pinned_fetch_array(array $keys, ?array $default = null): ?array {} +function pinned_fetch_array(array $keys, ?array $default = null, bool $throw_on_error = false): array|false {} -function pinned_exists(string $key): bool {} +function pinned_exists(string $key, bool $throw_on_error = false): bool {} -function pinned_lock(string $key, int $lease = 0): bool {} +function pinned_lock(string $key, int $lease = 0, bool $throw_on_error = false): bool {} -function pinned_unlock(string $key): bool {} +function pinned_unlock(string $key, bool $throw_on_error = false): bool {} -function pinned_delete(string $key_or_class): void {} +function pinned_delete(string $key_or_class, bool $throw_on_error = false): bool {} -function pinned_delete_array(array $keys): void {} +function pinned_delete_array(array $keys, bool $throw_on_error = false): bool {} -function pinned_clear(): void {} +function pinned_clear(bool $throw_on_error = false): bool {} -function pinned_atomic_increment(string $key, int $step = 1): int {} +function pinned_atomic_increment(string $key, int $step = 1, bool $throw_on_error = false): int|false {} -function pinned_atomic_decrement(string $key, int $step = 1): int {} +function pinned_atomic_decrement(string $key, int $step = 1, bool $throw_on_error = false): int|false {} function pinned_cache_info(): StaticCacheInfo {} diff --git a/ext/opcache/opcache_arginfo.h b/ext/opcache/opcache_arginfo.h index 5260a51bdc35..40064945f471 100644 --- a/ext/opcache/opcache_arginfo.h +++ b/ext/opcache/opcache_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit opcache.stub.php instead. - * Stub hash: fb25c489fa59f1e062bec1e2f711d9e81b19db69 */ + * Stub hash: 97f9dbdaf39f619254f05e89b18d48f5484f4fb0 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_reset, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -32,43 +32,52 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_store, 0, 2, _I ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_MASK(0, value, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, ttl, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_store_array, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, ttl, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_OPcache_volatile_fetch, 0, 1, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_MASK(0, default, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_fetch_array, 0, 1, IS_ARRAY, 1) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_OPcache_volatile_fetch_array, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, keys, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, default, IS_ARRAY, 1, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_exists, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_lock, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, lease, IS_LONG, 0, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() #define arginfo_OPcache_volatile_unlock arginfo_OPcache_volatile_exists -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_delete, 0, 1, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_delete, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, key_or_class, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_delete_array, 0, 1, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_delete_array, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, keys, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_clear, 0, 0, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_volatile_clear, 0, 0, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_OPcache_volatile_cache_info, 0, 0, OPcache\\StaticCacheInfo, 0) @@ -77,10 +86,12 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_store, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_MASK(0, value, MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT, NULL) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_store_array, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, values, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() #define arginfo_OPcache_pinned_fetch arginfo_OPcache_volatile_fetch @@ -99,9 +110,10 @@ ZEND_END_ARG_INFO() #define arginfo_OPcache_pinned_clear arginfo_OPcache_volatile_clear -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_OPcache_pinned_atomic_increment, 0, 1, IS_LONG, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_OPcache_pinned_atomic_increment, 0, 1, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, step, IS_LONG, 0, "1") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, throw_on_error, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() #define arginfo_OPcache_pinned_atomic_decrement arginfo_OPcache_pinned_atomic_increment diff --git a/ext/opcache/tests/static_cache_disabled_backends_info_001.phpt b/ext/opcache/tests/static_cache_disabled_backends_info_001.phpt index b15ff329dced..b32ad59be0ea 100644 --- a/ext/opcache/tests/static_cache_disabled_backends_info_001.phpt +++ b/ext/opcache/tests/static_cache_disabled_backends_info_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache static cache disabled backends report unavailable and store APIs return false +OPcache static cache disabled backends report unavailable and explicit APIs return false by default --EXTENSIONS-- opcache --INI-- @@ -12,22 +12,79 @@ opcache.static_cache.pinned_size_mb=0 function dump_info(string $label, OPcache\StaticCacheInfo $info): void { - echo $label, "\n"; - var_dump($info->enabled); - var_dump($info->available); - var_dump($info->startup_failed); - var_dump($info->backend_initialized); - var_dump($info->configured_memory); - var_dump($info->failure_reason); + echo $label, "\n"; + var_dump($info->enabled); + var_dump($info->available); + var_dump($info->startup_failed); + var_dump($info->backend_initialized); + var_dump($info->configured_memory); + var_dump($info->failure_reason); +} + +function dump_result(string $label, mixed $value): void +{ + echo $label, ': '; + var_dump($value); +} + +function dump_static_cache_exception(string $label, Closure $callback): void +{ + try { + $callback(); + echo $label, ": no exception\n"; + } catch (OPcache\StaticCacheException $exception) { + echo $label, ': ', get_class($exception), ': ', $exception->getMessage(), "\n"; + } } dump_info('volatile', OPcache\volatile_cache_info()); dump_info('pinned', OPcache\pinned_cache_info()); -var_dump(OPcache\volatile_store('key', 'value')); -var_dump(OPcache\volatile_store_array(['array-key' => 'value'])); -var_dump(OPcache\pinned_store('key', 'value')); -var_dump(OPcache\pinned_store_array(['array-key' => 'value'])); +dump_result('volatile_store', OPcache\volatile_store('key', 'value')); +dump_result('volatile_store_array', OPcache\volatile_store_array(['array-key' => 'value'])); +dump_result('volatile_fetch', OPcache\volatile_fetch('key', 'default')); +dump_result('volatile_fetch_array', OPcache\volatile_fetch_array(['key'], ['key' => 'default'])); +dump_result('volatile_exists', OPcache\volatile_exists('key')); +dump_result('volatile_lock', OPcache\volatile_lock('key')); +dump_result('volatile_unlock', OPcache\volatile_unlock('key')); +dump_result('volatile_delete', OPcache\volatile_delete('key')); +dump_result('volatile_delete_array', OPcache\volatile_delete_array(['key'])); +dump_result('volatile_clear', OPcache\volatile_clear()); +dump_result('pinned_store', OPcache\pinned_store('key', 'value')); +dump_result('pinned_store_array', OPcache\pinned_store_array(['array-key' => 'value'])); +dump_result('pinned_fetch', OPcache\pinned_fetch('key', 'default')); +dump_result('pinned_fetch_array', OPcache\pinned_fetch_array(['key'], ['key' => 'default'])); +dump_result('pinned_exists', OPcache\pinned_exists('key')); +dump_result('pinned_lock', OPcache\pinned_lock('key')); +dump_result('pinned_unlock', OPcache\pinned_unlock('key')); +dump_result('pinned_delete', OPcache\pinned_delete('key')); +dump_result('pinned_delete_array', OPcache\pinned_delete_array(['key'])); +dump_result('pinned_clear', OPcache\pinned_clear()); +dump_result('pinned_atomic_increment', OPcache\pinned_atomic_increment('key')); +dump_result('pinned_atomic_decrement', OPcache\pinned_atomic_decrement('key')); + +dump_static_cache_exception('volatile_store_throw', static fn () => OPcache\volatile_store('key', 'value', 0, true)); +dump_static_cache_exception('volatile_store_array_throw', static fn () => OPcache\volatile_store_array(['array-key' => 'value'], 0, true)); +dump_static_cache_exception('volatile_fetch_throw', static fn () => OPcache\volatile_fetch('key', 'default', true)); +dump_static_cache_exception('volatile_fetch_array_throw', static fn () => OPcache\volatile_fetch_array(['key'], ['key' => 'default'], true)); +dump_static_cache_exception('volatile_exists_throw', static fn () => OPcache\volatile_exists('key', true)); +dump_static_cache_exception('volatile_lock_throw', static fn () => OPcache\volatile_lock('key', 0, true)); +dump_static_cache_exception('volatile_unlock_throw', static fn () => OPcache\volatile_unlock('key', true)); +dump_static_cache_exception('volatile_delete_throw', static fn () => OPcache\volatile_delete('key', true)); +dump_static_cache_exception('volatile_delete_array_throw', static fn () => OPcache\volatile_delete_array(['key'], true)); +dump_static_cache_exception('volatile_clear_throw', static fn () => OPcache\volatile_clear(true)); +dump_static_cache_exception('pinned_store_throw', static fn () => OPcache\pinned_store('key', 'value', true)); +dump_static_cache_exception('pinned_store_array_throw', static fn () => OPcache\pinned_store_array(['array-key' => 'value'], true)); +dump_static_cache_exception('pinned_fetch_throw', static fn () => OPcache\pinned_fetch('key', 'default', true)); +dump_static_cache_exception('pinned_fetch_array_throw', static fn () => OPcache\pinned_fetch_array(['key'], ['key' => 'default'], true)); +dump_static_cache_exception('pinned_exists_throw', static fn () => OPcache\pinned_exists('key', true)); +dump_static_cache_exception('pinned_lock_throw', static fn () => OPcache\pinned_lock('key', 0, true)); +dump_static_cache_exception('pinned_unlock_throw', static fn () => OPcache\pinned_unlock('key', true)); +dump_static_cache_exception('pinned_delete_throw', static fn () => OPcache\pinned_delete('key', true)); +dump_static_cache_exception('pinned_delete_array_throw', static fn () => OPcache\pinned_delete_array(['key'], true)); +dump_static_cache_exception('pinned_clear_throw', static fn () => OPcache\pinned_clear(true)); +dump_static_cache_exception('pinned_atomic_increment_throw', static fn () => OPcache\pinned_atomic_increment('key', 1, true)); +dump_static_cache_exception('pinned_atomic_decrement_throw', static fn () => OPcache\pinned_atomic_decrement('key', 1, true)); ?> --EXPECT-- @@ -45,7 +102,47 @@ bool(false) bool(false) int(0) NULL -bool(false) -bool(false) -bool(false) -bool(false) +volatile_store: bool(false) +volatile_store_array: bool(false) +volatile_fetch: bool(false) +volatile_fetch_array: bool(false) +volatile_exists: bool(false) +volatile_lock: bool(false) +volatile_unlock: bool(false) +volatile_delete: bool(false) +volatile_delete_array: bool(false) +volatile_clear: bool(false) +pinned_store: bool(false) +pinned_store_array: bool(false) +pinned_fetch: bool(false) +pinned_fetch_array: bool(false) +pinned_exists: bool(false) +pinned_lock: bool(false) +pinned_unlock: bool(false) +pinned_delete: bool(false) +pinned_delete_array: bool(false) +pinned_clear: bool(false) +pinned_atomic_increment: bool(false) +pinned_atomic_decrement: bool(false) +volatile_store_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_store_array_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_fetch_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_fetch_array_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_exists_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_lock_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_unlock_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_delete_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_delete_array_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +volatile_clear_throw: OPcache\StaticCacheException: OPcache volatile cache is disabled +pinned_store_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_store_array_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_fetch_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_fetch_array_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_exists_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_lock_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_unlock_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_delete_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_delete_array_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_clear_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_atomic_increment_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled +pinned_atomic_decrement_throw: OPcache\StaticCacheException: OPcache pinned cache is disabled diff --git a/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt index fd920a16bb07..791e11d55438 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_fragmentation_relocation_skip_001.phpt @@ -30,8 +30,7 @@ function cache_store(string $kind, string $key, mixed $value): bool return OPcache\volatile_store($key, $value); } - OPcache\pinned_store($key, $value); - return true; + return OPcache\pinned_store($key, $value); } function cache_fetch(string $kind, string $key): mixed @@ -100,7 +99,7 @@ bool(true) bool(true) bool(true) bool(true) -OPcache\StaticCacheException: not enough shared memory left +bool(false) bool(false) int(1200000) int(1200000) diff --git a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt index 353be31290ec..52e2cef390c0 100644 --- a/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt +++ b/ext/opcache/tests/static_cache_explicit_cache_signatures_001.phpt @@ -25,6 +25,7 @@ function describe_type(?ReflectionType $type): string 'string' => 4, 'array' => 5, 'object' => 6, + 'false' => 7, ]; $names = array_map( static fn (ReflectionNamedType $named): string => $named->getName(), @@ -36,21 +37,29 @@ function describe_type(?ReflectionType $type): string } foreach ([ - 'OPcache\\volatile_store' => ['value'], - 'OPcache\\volatile_store_array' => [], - 'OPcache\\volatile_fetch' => ['default'], - 'OPcache\\volatile_fetch_array' => ['default'], - 'OPcache\\volatile_lock' => ['lease'], - 'OPcache\\volatile_unlock' => [], + 'OPcache\\volatile_store' => ['value', 'ttl', 'throw_on_error'], + 'OPcache\\volatile_store_array' => ['ttl', 'throw_on_error'], + 'OPcache\\volatile_fetch' => ['default', 'throw_on_error'], + 'OPcache\\volatile_fetch_array' => ['default', 'throw_on_error'], + 'OPcache\\volatile_exists' => ['throw_on_error'], + 'OPcache\\volatile_lock' => ['lease', 'throw_on_error'], + 'OPcache\\volatile_unlock' => ['throw_on_error'], + 'OPcache\\volatile_delete' => ['throw_on_error'], + 'OPcache\\volatile_delete_array' => ['throw_on_error'], + 'OPcache\\volatile_clear' => ['throw_on_error'], 'OPcache\\volatile_cache_info' => [], - 'OPcache\\pinned_store' => ['value'], - 'OPcache\\pinned_store_array' => [], - 'OPcache\\pinned_fetch' => ['default'], - 'OPcache\\pinned_fetch_array' => ['default'], - 'OPcache\\pinned_lock' => ['lease'], - 'OPcache\\pinned_unlock' => [], - 'OPcache\\pinned_atomic_increment' => ['step'], - 'OPcache\\pinned_atomic_decrement' => ['step'], + 'OPcache\\pinned_store' => ['value', 'throw_on_error'], + 'OPcache\\pinned_store_array' => ['throw_on_error'], + 'OPcache\\pinned_fetch' => ['default', 'throw_on_error'], + 'OPcache\\pinned_fetch_array' => ['default', 'throw_on_error'], + 'OPcache\\pinned_exists' => ['throw_on_error'], + 'OPcache\\pinned_lock' => ['lease', 'throw_on_error'], + 'OPcache\\pinned_unlock' => ['throw_on_error'], + 'OPcache\\pinned_delete' => ['throw_on_error'], + 'OPcache\\pinned_delete_array' => ['throw_on_error'], + 'OPcache\\pinned_clear' => ['throw_on_error'], + 'OPcache\\pinned_atomic_increment' => ['step', 'throw_on_error'], + 'OPcache\\pinned_atomic_decrement' => ['step', 'throw_on_error'], 'OPcache\\pinned_cache_info' => [], ] as $function => $parameters) { $reflection = new ReflectionFunction($function); @@ -70,19 +79,27 @@ foreach ([ ?> --EXPECT-- -OPcache\volatile_store $value=null|bool|int|float|string|array|object params=2/3 return=bool -OPcache\volatile_store_array params=1/2 return=bool -OPcache\volatile_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object -OPcache\volatile_fetch_array $default=?array params=1/2 return=?array -OPcache\volatile_lock $lease=int params=1/2 return=bool -OPcache\volatile_unlock params=1/1 return=bool +OPcache\volatile_store $value=null|bool|int|float|string|array|object $ttl=int $throw_on_error=bool params=2/4 return=bool +OPcache\volatile_store_array $ttl=int $throw_on_error=bool params=1/3 return=bool +OPcache\volatile_fetch $default=null|bool|int|float|string|array|object $throw_on_error=bool params=1/3 return=null|bool|int|float|string|array|object +OPcache\volatile_fetch_array $default=?array $throw_on_error=bool params=1/3 return=array|false +OPcache\volatile_exists $throw_on_error=bool params=1/2 return=bool +OPcache\volatile_lock $lease=int $throw_on_error=bool params=1/3 return=bool +OPcache\volatile_unlock $throw_on_error=bool params=1/2 return=bool +OPcache\volatile_delete $throw_on_error=bool params=1/2 return=bool +OPcache\volatile_delete_array $throw_on_error=bool params=1/2 return=bool +OPcache\volatile_clear $throw_on_error=bool params=0/1 return=bool OPcache\volatile_cache_info params=0/0 return=OPcache\StaticCacheInfo -OPcache\pinned_store $value=null|bool|int|float|string|array|object params=2/2 return=bool -OPcache\pinned_store_array params=1/1 return=bool -OPcache\pinned_fetch $default=null|bool|int|float|string|array|object params=1/2 return=null|bool|int|float|string|array|object -OPcache\pinned_fetch_array $default=?array params=1/2 return=?array -OPcache\pinned_lock $lease=int params=1/2 return=bool -OPcache\pinned_unlock params=1/1 return=bool -OPcache\pinned_atomic_increment $step=int params=1/2 return=int -OPcache\pinned_atomic_decrement $step=int params=1/2 return=int +OPcache\pinned_store $value=null|bool|int|float|string|array|object $throw_on_error=bool params=2/3 return=bool +OPcache\pinned_store_array $throw_on_error=bool params=1/2 return=bool +OPcache\pinned_fetch $default=null|bool|int|float|string|array|object $throw_on_error=bool params=1/3 return=null|bool|int|float|string|array|object +OPcache\pinned_fetch_array $default=?array $throw_on_error=bool params=1/3 return=array|false +OPcache\pinned_exists $throw_on_error=bool params=1/2 return=bool +OPcache\pinned_lock $lease=int $throw_on_error=bool params=1/3 return=bool +OPcache\pinned_unlock $throw_on_error=bool params=1/2 return=bool +OPcache\pinned_delete $throw_on_error=bool params=1/2 return=bool +OPcache\pinned_delete_array $throw_on_error=bool params=1/2 return=bool +OPcache\pinned_clear $throw_on_error=bool params=0/1 return=bool +OPcache\pinned_atomic_increment $step=int $throw_on_error=bool params=1/3 return=int|false +OPcache\pinned_atomic_decrement $step=int $throw_on_error=bool params=1/3 return=int|false OPcache\pinned_cache_info params=0/0 return=OPcache\StaticCacheInfo diff --git a/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt index 820ea0d93729..7d89959ce8fa 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_atomic_increment_create_001.phpt @@ -26,7 +26,7 @@ var_dump(OPcache\pinned_atomic_decrement('missing_down', 4)); var_dump(OPcache\pinned_fetch('missing_down')); try { - OPcache\pinned_atomic_increment('extra', 1, 1); + OPcache\pinned_atomic_increment('extra', 1, false, false); } catch (ArgumentCountError $exception) { echo "too-many-args\n"; } diff --git a/ext/opcache/tests/static_cache_persistent_cache_atomic_non_integer_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_atomic_non_integer_001.phpt index 4e93a00ffc27..86f9deb5df53 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_atomic_non_integer_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_atomic_non_integer_001.phpt @@ -13,19 +13,24 @@ use OPcache\StaticCacheException; OPcache\pinned_store('text', 'php'); +var_dump(OPcache\pinned_atomic_increment('text')); +var_dump(OPcache\pinned_atomic_decrement('text')); + try { - OPcache\pinned_atomic_increment('text'); + OPcache\pinned_atomic_increment('text', 1, true); } catch (StaticCacheException $exception) { echo $exception->getMessage(), "\n"; } try { - OPcache\pinned_atomic_decrement('text'); + OPcache\pinned_atomic_decrement('text', 1, true); } catch (StaticCacheException $exception) { echo $exception->getMessage(), "\n"; } ?> --EXPECT-- +bool(false) +bool(false) Atomic increment requires an integer value Atomic decrement requires an integer value diff --git a/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt index 2130b74fd82c..77f4c9463145 100644 --- a/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt +++ b/ext/opcache/tests/static_cache_persistent_cache_store_overflow_001.phpt @@ -1,5 +1,5 @@ --TEST-- -OPcache pinned_store throws StaticCacheException when pinned cache memory is exhausted +OPcache pinned_store throws StaticCacheException when throw_on_error is enabled and pinned cache memory is exhausted --EXTENSIONS-- opcache --INI-- @@ -13,8 +13,10 @@ use OPcache\StaticCacheException; OPcache\pinned_store('small', 'ok'); +var_dump(OPcache\pinned_store('huge', str_repeat('H', 12 * 1024 * 1024))); + try { - OPcache\pinned_store('huge', str_repeat('H', 12 * 1024 * 1024)); + OPcache\pinned_store('huge-throw', str_repeat('H', 12 * 1024 * 1024), true); } catch (StaticCacheException $e) { echo get_class($e), ': ', $e->getMessage(), "\n"; } @@ -24,6 +26,7 @@ var_dump(OPcache\pinned_fetch('missing', 'fallback')); ?> --EXPECT-- +bool(false) OPcache\StaticCacheException: not enough shared memory left string(2) "ok" string(8) "fallback" diff --git a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt index ef658ef2de62..7a95906a46ac 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_direct_cache_safe_unstorable_001.phpt @@ -40,8 +40,8 @@ var_dump(OPcache\volatile_fetch('fixed-resource', 'missing')); var_dump(OPcache\volatile_store('array-object-closure', $array_object)); var_dump(OPcache\volatile_fetch('array-object-closure', 'missing')); -dump_pinned_exception(static fn () => OPcache\pinned_store('fixed-resource', $fixed_array)); -dump_pinned_exception(static fn () => OPcache\pinned_store('array-object-closure', $array_object)); +dump_pinned_exception(static fn () => OPcache\pinned_store('fixed-resource', $fixed_array, true)); +dump_pinned_exception(static fn () => OPcache\pinned_store('array-object-closure', $array_object, true)); fclose($resource); diff --git a/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt index ef9e00aa4320..202664751264 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_storable_values_001.phpt @@ -94,18 +94,18 @@ dump_type_error(static fn () => OPcache\volatile_fetch('missing', $closure)); dump_type_error(static fn () => OPcache\pinned_store('resource', $resource)); dump_type_error(static fn () => OPcache\pinned_store('closure-value', $closure)); -dump_static_cache_exception(static fn () => OPcache\pinned_store_array(['nested-resource' => ['value' => $resource]])); -dump_static_cache_exception(static fn () => OPcache\pinned_store_array(['nested-closure' => ['value' => $closure]])); -dump_static_cache_exception(static fn () => OPcache\pinned_store('object-resource', $resource_object)); -dump_static_cache_exception(static fn () => OPcache\pinned_store('object-closure', $closure_object)); -dump_static_cache_exception(static fn () => OPcache\pinned_store('spl-resource', $resource_fixed_array)); -dump_static_cache_exception(static fn () => OPcache\pinned_store('spl-closure', $closure_fixed_array)); -dump_static_cache_exception(static fn () => OPcache\pinned_store('array-object-resource', $resource_array_object)); -dump_static_cache_exception(static fn () => OPcache\pinned_store('array-object-closure', $closure_array_object)); +dump_static_cache_exception(static fn () => OPcache\pinned_store_array(['nested-resource' => ['value' => $resource]], true)); +dump_static_cache_exception(static fn () => OPcache\pinned_store_array(['nested-closure' => ['value' => $closure]], true)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('object-resource', $resource_object, true)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('object-closure', $closure_object, true)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('spl-resource', $resource_fixed_array, true)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('spl-closure', $closure_fixed_array, true)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('array-object-resource', $resource_array_object, true)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('array-object-closure', $closure_array_object, true)); StaticCacheUnsupportedSerializedPayload::$value = $resource; -dump_static_cache_exception(static fn () => OPcache\pinned_store('serialized-resource', $serialized_payload)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('serialized-resource', $serialized_payload, true)); StaticCacheUnsupportedSerializedPayload::$value = $closure; -dump_static_cache_exception(static fn () => OPcache\pinned_store('serialized-closure', $serialized_payload)); +dump_static_cache_exception(static fn () => OPcache\pinned_store('serialized-closure', $serialized_payload, true)); dump_type_error(static fn () => OPcache\pinned_fetch('missing', $resource)); dump_type_error(static fn () => OPcache\pinned_fetch('missing', $closure)); diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index c348e7247558..6e2bad952fe1 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -532,35 +532,43 @@ static zend_always_inline bool zend_opcache_static_cache_parse_lease(zend_long l return true; } -static zend_always_inline bool zend_opcache_static_cache_require_available_read(void) +static zend_always_inline bool zend_opcache_static_cache_require_available_read(bool throw_on_error) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); if (!zend_opcache_validate_api_restriction()) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + } return false; } if (!zend_opcache_static_cache_active_runtime()->available) { - if (zend_opcache_static_cache_active_runtime()->failure_reason) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", zend_opcache_static_cache_active_runtime()->failure_reason); - } else { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "OPcache %s is disabled", context->name); + if (throw_on_error) { + if (zend_opcache_static_cache_active_runtime()->failure_reason) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", zend_opcache_static_cache_active_runtime()->failure_reason); + } else { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "OPcache %s is disabled", context->name); + } } return false; } if (!zend_opcache_static_cache_rlock()) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s read lock", context->name); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s read lock", context->name); + } return false; } if (!zend_opcache_static_cache_header_is_initialized_locked()) { zend_opcache_static_cache_unlock(); - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to access the %s header", context->name); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to access the %s header", context->name); + } return false; } @@ -568,21 +576,25 @@ static zend_always_inline bool zend_opcache_static_cache_require_available_read( return true; } -static zend_always_inline bool zend_opcache_static_cache_validate_available_write(void) +static zend_always_inline bool zend_opcache_static_cache_validate_available_write(bool throw_on_error) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); if (!zend_opcache_validate_api_restriction()) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + } return false; } if (!zend_opcache_static_cache_active_runtime()->available) { - if (zend_opcache_static_cache_active_runtime()->failure_reason) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", zend_opcache_static_cache_active_runtime()->failure_reason); - } else { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "OPcache %s is disabled", context->name); + if (throw_on_error) { + if (zend_opcache_static_cache_active_runtime()->failure_reason) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", zend_opcache_static_cache_active_runtime()->failure_reason); + } else { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "OPcache %s is disabled", context->name); + } } return false; @@ -591,30 +603,50 @@ static zend_always_inline bool zend_opcache_static_cache_validate_available_writ return true; } -static zend_always_inline bool zend_opcache_static_cache_store_backend_available(void) +static zend_always_inline bool zend_opcache_static_cache_store_backend_available(bool throw_on_error) { + zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); + if (!zend_opcache_validate_api_restriction()) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, ACCELERATOR_PRODUCT_NAME " API access is restricted"); + } return false; } - return zend_opcache_static_cache_active_runtime()->available; + if (!zend_opcache_static_cache_active_runtime()->available) { + if (throw_on_error) { + if (zend_opcache_static_cache_active_runtime()->failure_reason) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", zend_opcache_static_cache_active_runtime()->failure_reason); + } else { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "OPcache %s is disabled", context->name); + } + } + + return false; + } + + return true; } -static zend_always_inline bool zend_opcache_static_cache_acquire_write_lock(void) +static zend_always_inline bool zend_opcache_static_cache_acquire_write_lock(bool throw_on_error) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); if (!zend_opcache_static_cache_wlock()) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s lock", context->name); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s lock", context->name); + } return false; } if (!zend_opcache_static_cache_header_init_locked()) { zend_opcache_static_cache_unlock(); - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to initialize the %s header", context->name); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to initialize the %s header", context->name); + } return false; } @@ -622,22 +654,13 @@ static zend_always_inline bool zend_opcache_static_cache_acquire_write_lock(void return true; } -static zend_always_inline bool zend_opcache_static_cache_require_available_write(void) -{ - if (!zend_opcache_static_cache_validate_available_write()) { - return false; - } - - return zend_opcache_static_cache_acquire_write_lock(); -} - -static zend_always_inline bool zend_opcache_static_cache_begin_entry_mutation(zend_string *key, bool *release_entry_lock) +static zend_always_inline bool zend_opcache_static_cache_begin_entry_mutation(zend_string *key, bool *release_entry_lock, bool throw_on_error) { ZEND_ASSERT(release_entry_lock != NULL); *release_entry_lock = false; if (!zend_opcache_static_cache_has_entry_lock(key)) { - if (!zend_opcache_static_cache_acquire_entry_lock(key)) { + if (!zend_opcache_static_cache_acquire_entry_lock(key, throw_on_error)) { return false; } @@ -654,13 +677,13 @@ static zend_always_inline void zend_opcache_static_cache_finish_entry_mutation(z } } -static zend_always_inline bool zend_opcache_static_cache_explicit_delete_prevalidated(zend_string *key) +static zend_always_inline bool zend_opcache_static_cache_explicit_delete_prevalidated(zend_string *key, bool throw_on_error) { bool deleted, release_entry_lock; release_entry_lock = zend_opcache_static_cache_has_entry_lock(key); - if (!zend_opcache_static_cache_acquire_write_lock()) { + if (!zend_opcache_static_cache_acquire_write_lock(throw_on_error)) { return false; } @@ -670,17 +693,17 @@ static zend_always_inline bool zend_opcache_static_cache_explicit_delete_prevali zend_opcache_static_cache_release_entry_lock(key); } - return deleted; + return true; } -static zend_always_inline bool zend_opcache_static_cache_explicit_delete_or_class_prevalidated(zend_string *key_or_class) +static zend_always_inline bool zend_opcache_static_cache_explicit_delete_or_class_prevalidated(zend_string *key_or_class, bool throw_on_error) { zend_class_entry *ce; ce = zend_opcache_static_cache_lookup_loaded_class_key(key_or_class); if (ce != NULL) { if (zend_opcache_static_cache_class_has_keys(ce)) { - if (!zend_opcache_static_cache_acquire_write_lock()) { + if (!zend_opcache_static_cache_acquire_write_lock(throw_on_error)) { return false; } @@ -691,14 +714,14 @@ static zend_always_inline bool zend_opcache_static_cache_explicit_delete_or_clas return true; } - return zend_opcache_static_cache_explicit_delete_prevalidated(key_or_class); + return zend_opcache_static_cache_explicit_delete_prevalidated(key_or_class, throw_on_error); } -static zend_always_inline bool zend_opcache_static_cache_explicit_clear_prevalidated(void) +static zend_always_inline bool zend_opcache_static_cache_explicit_clear_prevalidated(bool throw_on_error) { bool cleared; - if (!zend_opcache_static_cache_acquire_write_lock()) { + if (!zend_opcache_static_cache_acquire_write_lock(throw_on_error)) { return false; } @@ -711,16 +734,16 @@ static zend_always_inline bool zend_opcache_static_cache_explicit_clear_prevalid return cleared; } -static zend_always_inline bool zend_opcache_static_cache_explicit_store_prevalidated(zend_string *key, zval *value, zend_long ttl, bool throw_on_failure) +static zend_always_inline bool zend_opcache_static_cache_explicit_store_prevalidated(zend_string *key, zval *value, zend_long ttl, bool throw_on_error) { zend_opcache_static_cache_prepared_value prepared; bool stored, release_entry_lock = false; - if (!zend_opcache_static_cache_prepare_value(&prepared, key, value, throw_on_failure, false)) { + if (!zend_opcache_static_cache_prepare_value(&prepared, key, value, throw_on_error, false, false)) { return false; } - if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock)) { + if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock, throw_on_error)) { zend_opcache_static_cache_destroy_prepared_value(&prepared); return false; @@ -729,7 +752,7 @@ static zend_always_inline bool zend_opcache_static_cache_explicit_store_prevalid /* Callers validate availability before staging the value so large shared-graph * builds stay outside the write lock. Once that preflight passed, the commit * path only needs to acquire the lock and publish the prepared payload. */ - if (!zend_opcache_static_cache_acquire_write_lock()) { + if (!zend_opcache_static_cache_acquire_write_lock(throw_on_error)) { zend_opcache_static_cache_destroy_prepared_value(&prepared); if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); @@ -738,7 +761,7 @@ static zend_always_inline bool zend_opcache_static_cache_explicit_store_prevalid return false; } - stored = zend_opcache_static_cache_store_prepared_locked(key, value, &prepared, ttl, throw_on_failure); + stored = zend_opcache_static_cache_store_prepared_locked(key, value, &prepared, ttl, throw_on_error, false); zend_opcache_static_cache_unlock(); zend_opcache_static_cache_destroy_prepared_value(&prepared); zend_opcache_static_cache_finish_entry_mutation(key, release_entry_lock, stored); @@ -750,13 +773,14 @@ static zend_always_inline zend_result zend_opcache_static_cache_fetch_api( zend_opcache_static_cache_context *context, zend_string *key, zval *default_value, - zval *return_value) + zval *return_value, + bool throw_on_error) { zend_opcache_static_cache_context *previous_context; bool fetched, found = false; previous_context = zend_opcache_static_cache_activate_context(context); - if (!zend_opcache_static_cache_require_available_read()) { + if (!zend_opcache_static_cache_require_available_read(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); return FAILURE; @@ -773,7 +797,7 @@ static zend_always_inline zend_result zend_opcache_static_cache_fetch_api( return SUCCESS; } - if (!EG(exception)) { + if (throw_on_error && !EG(exception)) { zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" could not be decoded", context->name, ZSTR_VAL(key)); } @@ -787,7 +811,8 @@ static zend_always_inline zend_result zend_opcache_static_cache_fetch_array_api( zend_opcache_static_cache_context *context, HashTable *keys, zval *default_value, - zval *return_value) + zval *return_value, + bool throw_on_error) { zend_opcache_static_cache_context *previous_context; zend_string **prepared_keys, *key; @@ -800,7 +825,7 @@ static zend_always_inline zend_result zend_opcache_static_cache_fetch_array_api( } previous_context = zend_opcache_static_cache_activate_context(context); - if (!zend_opcache_static_cache_require_available_read()) { + if (!zend_opcache_static_cache_require_available_read(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); zend_opcache_static_cache_release_key_list(prepared_keys, key_count); @@ -816,7 +841,7 @@ static zend_always_inline zend_result zend_opcache_static_cache_fetch_array_api( if (!found) { ZVAL_COPY(&fetched_value, default_value); } else { - if (!EG(exception)) { + if (throw_on_error && !EG(exception)) { zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" could not be decoded", context->name, ZSTR_VAL(key)); } zval_ptr_dtor(return_value); @@ -839,12 +864,12 @@ static zend_always_inline zend_result zend_opcache_static_cache_fetch_array_api( return SUCCESS; } -static zend_always_inline zend_result zend_opcache_static_cache_exists_api(zend_opcache_static_cache_context *context, zend_string *key, bool *exists) +static zend_always_inline zend_result zend_opcache_static_cache_exists_api(zend_opcache_static_cache_context *context, zend_string *key, bool *exists, bool throw_on_error) { zend_opcache_static_cache_context *previous_context; previous_context = zend_opcache_static_cache_activate_context(context); - if (!zend_opcache_static_cache_require_available_read()) { + if (!zend_opcache_static_cache_require_available_read(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); return FAILURE; @@ -861,12 +886,13 @@ static zend_always_inline zend_result zend_opcache_static_cache_lock_api( zend_opcache_static_cache_context *context, zend_string *key, zend_long lease, - bool *locked) + bool *locked, + bool throw_on_error) { zend_opcache_static_cache_context *previous_context; previous_context = zend_opcache_static_cache_activate_context(context); - if (!zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); return FAILURE; @@ -878,12 +904,12 @@ static zend_always_inline zend_result zend_opcache_static_cache_lock_api( return SUCCESS; } -static zend_always_inline zend_result zend_opcache_static_cache_unlock_api(zend_opcache_static_cache_context *context, zend_string *key, bool *unlocked) +static zend_always_inline zend_result zend_opcache_static_cache_unlock_api(zend_opcache_static_cache_context *context, zend_string *key, bool *unlocked, bool throw_on_error) { zend_opcache_static_cache_context *previous_context; previous_context = zend_opcache_static_cache_activate_context(context); - if (!zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); return FAILURE; @@ -1199,13 +1225,15 @@ ZEND_FUNCTION(OPcache_volatile_store) zend_string *key; zend_long ttl = 0; zval *value; + bool throw_on_error = false; bool stored; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR(key) Z_PARAM_ZVAL(value) Z_PARAM_OPTIONAL Z_PARAM_LONG(ttl) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { @@ -1221,7 +1249,7 @@ ZEND_FUNCTION(OPcache_volatile_store) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); - if (!zend_opcache_static_cache_store_backend_available()) { + if (!zend_opcache_static_cache_store_backend_available(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); if (EG(exception)) { RETURN_THROWS(); @@ -1230,7 +1258,7 @@ ZEND_FUNCTION(OPcache_volatile_store) RETURN_FALSE; } - stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, false); + stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, throw_on_error); zend_opcache_static_cache_restore_context(previous_context); if (!stored) { @@ -1251,12 +1279,14 @@ ZEND_FUNCTION(OPcache_volatile_store_array) zend_long ttl = 0; zval *value; HashTable *values; + bool throw_on_error = false; bool stored; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ARRAY_HT(values) Z_PARAM_OPTIONAL Z_PARAM_LONG(ttl) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_parse_ttl(ttl, 2)) { @@ -1268,7 +1298,7 @@ ZEND_FUNCTION(OPcache_volatile_store_array) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); - if (!zend_opcache_static_cache_store_backend_available()) { + if (!zend_opcache_static_cache_store_backend_available(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); if (EG(exception)) { RETURN_THROWS(); @@ -1280,7 +1310,7 @@ ZEND_FUNCTION(OPcache_volatile_store_array) ZEND_HASH_FOREACH_STR_KEY_VAL(values, key, value) { ZEND_ASSERT(key != NULL); - stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, false); + stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, ttl, throw_on_error); if (!stored) { zend_opcache_static_cache_restore_context(previous_context); if (EG(exception)) { @@ -1300,11 +1330,13 @@ ZEND_FUNCTION(OPcache_volatile_fetch) { zend_string *key; zval *default_value = NULL, default_null; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(default_value) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { @@ -1320,8 +1352,12 @@ ZEND_FUNCTION(OPcache_volatile_fetch) RETURN_THROWS(); } - if (zend_opcache_static_cache_fetch_api(&zend_opcache_static_cache_volatile_context_state, key, default_value, return_value) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_fetch_api(&zend_opcache_static_cache_volatile_context_state, key, default_value, return_value, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } } @@ -1329,11 +1365,13 @@ ZEND_FUNCTION(OPcache_volatile_fetch_array) { HashTable *keys; zval *default_value = NULL, default_null; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ARRAY_HT(keys) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_OR_NULL(default_value) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (default_value == NULL) { @@ -1341,26 +1379,37 @@ ZEND_FUNCTION(OPcache_volatile_fetch_array) default_value = &default_null; } - if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_volatile_context_state, keys, default_value, return_value) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_volatile_context_state, keys, default_value, return_value, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } } ZEND_FUNCTION(OPcache_volatile_exists) { zend_string *key; + bool throw_on_error = false; bool exists; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { RETURN_THROWS(); } - if (zend_opcache_static_cache_exists_api(&zend_opcache_static_cache_volatile_context_state, key, &exists) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_exists_api(&zend_opcache_static_cache_volatile_context_state, key, &exists, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } RETURN_BOOL(exists); @@ -1370,12 +1419,14 @@ ZEND_FUNCTION(OPcache_volatile_lock) { zend_string *key; zend_long lease = 0; + bool throw_on_error = false; bool locked; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) Z_PARAM_OPTIONAL Z_PARAM_LONG(lease) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { @@ -1386,8 +1437,12 @@ ZEND_FUNCTION(OPcache_volatile_lock) RETURN_THROWS(); } - if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_volatile_context_state, key, lease, &locked) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_volatile_context_state, key, lease, &locked, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } RETURN_BOOL(locked); @@ -1396,18 +1451,25 @@ ZEND_FUNCTION(OPcache_volatile_lock) ZEND_FUNCTION(OPcache_volatile_unlock) { zend_string *key; + bool throw_on_error = false; bool unlocked; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { RETURN_THROWS(); } - if (zend_opcache_static_cache_unlock_api(&zend_opcache_static_cache_volatile_context_state, key, &unlocked) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_unlock_api(&zend_opcache_static_cache_volatile_context_state, key, &unlocked, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } RETURN_BOOL(unlocked); @@ -1416,22 +1478,35 @@ ZEND_FUNCTION(OPcache_volatile_unlock) ZEND_FUNCTION(OPcache_volatile_delete) { zend_string *key_or_class; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key_or_class) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key_or_class(key_or_class, 1)) { RETURN_THROWS(); } - if (!zend_opcache_static_cache_validate_available_write()) { - RETURN_THROWS(); + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } - if (!zend_opcache_static_cache_explicit_delete_or_class_prevalidated(key_or_class)) { - RETURN_THROWS(); + if (!zend_opcache_static_cache_explicit_delete_or_class_prevalidated(key_or_class, throw_on_error)) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } + + RETURN_TRUE; } ZEND_FUNCTION(OPcache_volatile_delete_array) @@ -1439,47 +1514,72 @@ ZEND_FUNCTION(OPcache_volatile_delete_array) zend_string **prepared_keys; HashTable *keys; uint32_t key_count, index; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY_HT(keys) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_prepare_key_list(keys, &prepared_keys, &key_count, 1)) { RETURN_THROWS(); } - if (!zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } for (index = 0; index < key_count; index++) { - if (!zend_opcache_static_cache_explicit_delete_prevalidated(prepared_keys[index])) { + if (!zend_opcache_static_cache_explicit_delete_prevalidated(prepared_keys[index], throw_on_error)) { zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } } zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + RETURN_TRUE; } ZEND_FUNCTION(OPcache_volatile_clear) { - ZEND_PARSE_PARAMETERS_NONE(); + bool throw_on_error = false; - if (!zend_opcache_static_cache_validate_available_write()) { - RETURN_THROWS(); + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } - if (!zend_opcache_static_cache_explicit_clear_prevalidated()) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the volatile cache"); + if (!zend_opcache_static_cache_explicit_clear_prevalidated(throw_on_error)) { + if (throw_on_error && !EG(exception)) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the volatile cache"); + } + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } zend_opcache_static_cache_mark_publish_skipped(&zend_opcache_static_cache_volatile_context_state); + RETURN_TRUE; } ZEND_FUNCTION(OPcache_volatile_cache_info) @@ -1504,11 +1604,14 @@ ZEND_FUNCTION(OPcache_pinned_store) zend_opcache_static_cache_context *previous_context; zend_string *key; zval *value; + bool throw_on_error = false; bool stored; - ZEND_PARSE_PARAMETERS_START(2, 2) + ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_STR(key) Z_PARAM_ZVAL(value) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { @@ -1520,7 +1623,7 @@ ZEND_FUNCTION(OPcache_pinned_store) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_store_backend_available()) { + if (!zend_opcache_static_cache_store_backend_available(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); if (EG(exception)) { RETURN_THROWS(); @@ -1529,11 +1632,15 @@ ZEND_FUNCTION(OPcache_pinned_store) RETURN_FALSE; } - stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, true); + stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, throw_on_error); zend_opcache_static_cache_restore_context(previous_context); if (!stored) { - RETURN_THROWS(); + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } RETURN_TRUE; @@ -1545,10 +1652,13 @@ ZEND_FUNCTION(OPcache_pinned_store_array) zend_string *key; zval *value; HashTable *values; + bool throw_on_error = false; bool stored; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY_HT(values) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_store_array_keys(values, 1)) { @@ -1556,7 +1666,7 @@ ZEND_FUNCTION(OPcache_pinned_store_array) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_store_backend_available()) { + if (!zend_opcache_static_cache_store_backend_available(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); if (EG(exception)) { RETURN_THROWS(); @@ -1568,11 +1678,14 @@ ZEND_FUNCTION(OPcache_pinned_store_array) ZEND_HASH_FOREACH_STR_KEY_VAL(values, key, value) { ZEND_ASSERT(key != NULL); - stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, true); + stored = zend_opcache_static_cache_explicit_store_prevalidated(key, value, 0, throw_on_error); if (!stored) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } } ZEND_HASH_FOREACH_END(); @@ -1585,11 +1698,13 @@ ZEND_FUNCTION(OPcache_pinned_fetch) { zend_string *key; zval *default_value = NULL, default_null; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(default_value) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { @@ -1605,8 +1720,12 @@ ZEND_FUNCTION(OPcache_pinned_fetch) RETURN_THROWS(); } - if (zend_opcache_static_cache_fetch_api(&zend_opcache_static_cache_pinned_context_state, key, default_value, return_value) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_fetch_api(&zend_opcache_static_cache_pinned_context_state, key, default_value, return_value, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } } @@ -1614,11 +1733,13 @@ ZEND_FUNCTION(OPcache_pinned_fetch_array) { HashTable *keys; zval *default_value = NULL, default_null; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ARRAY_HT(keys) Z_PARAM_OPTIONAL Z_PARAM_ARRAY_OR_NULL(default_value) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (default_value == NULL) { @@ -1626,26 +1747,37 @@ ZEND_FUNCTION(OPcache_pinned_fetch_array) default_value = &default_null; } - if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_pinned_context_state, keys, default_value, return_value) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_pinned_context_state, keys, default_value, return_value, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } } ZEND_FUNCTION(OPcache_pinned_exists) { zend_string *key; + bool throw_on_error = false; bool exists; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { RETURN_THROWS(); } - if (zend_opcache_static_cache_exists_api(&zend_opcache_static_cache_pinned_context_state, key, &exists) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_exists_api(&zend_opcache_static_cache_pinned_context_state, key, &exists, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } RETURN_BOOL(exists); @@ -1655,12 +1787,14 @@ ZEND_FUNCTION(OPcache_pinned_lock) { zend_string *key; zend_long lease = 0; + bool throw_on_error = false; bool locked; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) Z_PARAM_OPTIONAL Z_PARAM_LONG(lease) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { @@ -1671,8 +1805,12 @@ ZEND_FUNCTION(OPcache_pinned_lock) RETURN_THROWS(); } - if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_pinned_context_state, key, lease, &locked) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_lock_api(&zend_opcache_static_cache_pinned_context_state, key, lease, &locked, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } RETURN_BOOL(locked); @@ -1681,18 +1819,25 @@ ZEND_FUNCTION(OPcache_pinned_lock) ZEND_FUNCTION(OPcache_pinned_unlock) { zend_string *key; + bool throw_on_error = false; bool unlocked; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { RETURN_THROWS(); } - if (zend_opcache_static_cache_unlock_api(&zend_opcache_static_cache_pinned_context_state, key, &unlocked) == FAILURE) { - RETURN_THROWS(); + if (zend_opcache_static_cache_unlock_api(&zend_opcache_static_cache_pinned_context_state, key, &unlocked, throw_on_error) == FAILURE) { + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } RETURN_BOOL(unlocked); @@ -1702,9 +1847,12 @@ ZEND_FUNCTION(OPcache_pinned_delete) { zend_opcache_static_cache_context *previous_context; zend_string *key_or_class; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key_or_class) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key_or_class(key_or_class, 1)) { @@ -1712,18 +1860,25 @@ ZEND_FUNCTION(OPcache_pinned_delete) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } - if (!zend_opcache_static_cache_explicit_delete_or_class_prevalidated(key_or_class)) { + if (!zend_opcache_static_cache_explicit_delete_or_class_prevalidated(key_or_class, throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } zend_opcache_static_cache_restore_context(previous_context); + RETURN_TRUE; } ZEND_FUNCTION(OPcache_pinned_delete_array) @@ -1732,9 +1887,12 @@ ZEND_FUNCTION(OPcache_pinned_delete_array) zend_string **prepared_keys; HashTable *keys; uint32_t key_count, index; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY_HT(keys) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_prepare_key_list(keys, &prepared_keys, &key_count, 1)) { @@ -1742,48 +1900,68 @@ ZEND_FUNCTION(OPcache_pinned_delete_array) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } for (index = 0; index < key_count; index++) { - if (!zend_opcache_static_cache_explicit_delete_prevalidated(prepared_keys[index])) { + if (!zend_opcache_static_cache_explicit_delete_prevalidated(prepared_keys[index], throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } } zend_opcache_static_cache_restore_context(previous_context); zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + RETURN_TRUE; } ZEND_FUNCTION(OPcache_pinned_clear) { zend_opcache_static_cache_context *previous_context; + bool throw_on_error = false; - ZEND_PARSE_PARAMETERS_NONE(); + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_on_error) + ZEND_PARSE_PARAMETERS_END(); previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_validate_available_write()) { + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } - if (!zend_opcache_static_cache_explicit_clear_prevalidated()) { + if (!zend_opcache_static_cache_explicit_clear_prevalidated(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the pinned cache"); + if (throw_on_error && !EG(exception)) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the pinned cache"); + } + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } zend_opcache_static_cache_mark_publish_skipped(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_restore_context(previous_context); + RETURN_TRUE; } ZEND_FUNCTION(OPcache_pinned_atomic_increment) @@ -1791,12 +1969,14 @@ ZEND_FUNCTION(OPcache_pinned_atomic_increment) zend_opcache_static_cache_context *previous_context; zend_long step = 1, new_value; zend_string *key; + bool throw_on_error = false; bool release_entry_lock = false; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) Z_PARAM_OPTIONAL Z_PARAM_LONG(step) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { @@ -1804,29 +1984,47 @@ ZEND_FUNCTION(OPcache_pinned_atomic_increment) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock)) { + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; + } + + if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock, throw_on_error)) { + zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } - if (!zend_opcache_static_cache_require_available_write()) { + if (!zend_opcache_static_cache_acquire_write_lock(throw_on_error)) { if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); } zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } - if (!zend_opcache_static_cache_atomic_update_locked(key, step, false, true, &new_value, "Atomic increment requires an integer value")) { + if (!zend_opcache_static_cache_atomic_update_locked(key, step, false, true, &new_value, "Atomic increment requires an integer value", throw_on_error)) { zend_opcache_static_cache_unlock(); if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); } zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } RETVAL_LONG(new_value); @@ -1840,12 +2038,14 @@ ZEND_FUNCTION(OPcache_pinned_atomic_decrement) zend_opcache_static_cache_context *previous_context; zend_string *key; zend_long step = 1, new_value; + bool throw_on_error = false; bool release_entry_lock = false; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) Z_PARAM_OPTIONAL Z_PARAM_LONG(step) + Z_PARAM_BOOL(throw_on_error) ZEND_PARSE_PARAMETERS_END(); if (!zend_opcache_static_cache_validate_key(key, 1)) { @@ -1853,29 +2053,47 @@ ZEND_FUNCTION(OPcache_pinned_atomic_decrement) } previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); - if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock)) { + if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; + } + + if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock, throw_on_error)) { + zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } + + RETURN_FALSE; } - if (!zend_opcache_static_cache_require_available_write()) { + if (!zend_opcache_static_cache_acquire_write_lock(throw_on_error)) { if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); } zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } - if (!zend_opcache_static_cache_atomic_update_locked(key, step, true, true, &new_value, "Atomic decrement requires an integer value")) { + if (!zend_opcache_static_cache_atomic_update_locked(key, step, true, true, &new_value, "Atomic decrement requires an integer value", throw_on_error)) { zend_opcache_static_cache_unlock(); if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); } zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { + RETURN_THROWS(); + } - RETURN_THROWS(); + RETURN_FALSE; } RETVAL_LONG(new_value); diff --git a/ext/opcache/zend_static_cache_entries.c b/ext/opcache/zend_static_cache_entries.c index 6684871770dc..21b8db4160dd 100644 --- a/ext/opcache/zend_static_cache_entries.c +++ b/ext/opcache/zend_static_cache_entries.c @@ -911,11 +911,11 @@ static bool zend_opcache_static_cache_expunge_expired_locked(void) return removed; } -static void zend_opcache_static_cache_handle_store_failure(const char *failure_message, bool throw_on_failure) +static void zend_opcache_static_cache_handle_store_failure(const char *failure_message, bool throw_on_failure, bool honor_strict_store_failure) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); - if (context->strict_store_failure && !throw_on_failure) { + if (honor_strict_store_failure && context->strict_store_failure && !throw_on_failure) { zend_opcache_static_cache_mark_publish_skipped(context); zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", failure_message); @@ -1008,7 +1008,7 @@ static bool zend_opcache_static_cache_find_unstorable_value( return false; } -static bool zend_opcache_static_cache_validate_storable_value(zval *value, bool throw_on_failure) +static bool zend_opcache_static_cache_validate_storable_value(zval *value, bool throw_on_failure, bool honor_strict_store_failure) { const char *failure_message = NULL; HashTable seen_arrays, seen_objects; @@ -1033,7 +1033,7 @@ static bool zend_opcache_static_cache_validate_storable_value(zval *value, bool } if (failure_message != NULL) { - zend_opcache_static_cache_handle_store_failure(failure_message, throw_on_failure); + zend_opcache_static_cache_handle_store_failure(failure_message, throw_on_failure, honor_strict_store_failure); } return false; @@ -1171,6 +1171,7 @@ bool zend_opcache_static_cache_prepare_value( zend_string *key, zval *value, bool throw_on_failure, + bool honor_strict_store_failure, bool lock_held) { php_serialize_data_t var_hash; @@ -1190,7 +1191,8 @@ bool zend_opcache_static_cache_prepare_value( if (Z_TYPE_P(value) == IS_RESOURCE) { zend_opcache_static_cache_handle_store_failure( "resources cannot be stored in the static cache", - throw_on_failure + throw_on_failure, + honor_strict_store_failure ); return false; @@ -1199,13 +1201,14 @@ bool zend_opcache_static_cache_prepare_value( if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ce_closure) { zend_opcache_static_cache_handle_store_failure( "Closure objects cannot be stored in the static cache", - throw_on_failure + throw_on_failure, + honor_strict_store_failure ); return false; } - if (!zend_opcache_static_cache_validate_storable_value(value, throw_on_failure)) { + if (!zend_opcache_static_cache_validate_storable_value(value, throw_on_failure, honor_strict_store_failure)) { return false; } @@ -1300,7 +1303,8 @@ bool zend_opcache_static_cache_prepare_value( if (failed_unstorable) { zend_opcache_static_cache_handle_store_failure( "resources and Closure objects cannot be stored in the static cache", - throw_on_failure + throw_on_failure, + honor_strict_store_failure ); return false; @@ -1320,7 +1324,8 @@ bool zend_opcache_static_cache_prepare_value( zend_opcache_static_cache_handle_store_failure( "failed to serialize value for cache storage", - throw_on_failure + throw_on_failure, + honor_strict_store_failure ); return false; @@ -1360,7 +1365,8 @@ bool zend_opcache_static_cache_store_prepared_locked( zval *value, const zend_opcache_static_cache_prepared_value *prepared, zend_long ttl, - bool throw_on_failure) + bool throw_on_failure, + bool honor_strict_store_failure) { const char *failure_message; zend_opcache_static_cache_header *header; @@ -1407,7 +1413,7 @@ bool zend_opcache_static_cache_store_prepared_locked( goto retry_store; } - zend_opcache_static_cache_handle_store_failure("cache hash table is full", throw_on_failure); + zend_opcache_static_cache_handle_store_failure("cache hash table is full", throw_on_failure, honor_strict_store_failure); return false; } @@ -1756,12 +1762,12 @@ bool zend_opcache_static_cache_store_prepared_locked( goto retry_store; } - zend_opcache_static_cache_handle_store_failure(failure_message, throw_on_failure); + zend_opcache_static_cache_handle_store_failure(failure_message, throw_on_failure, honor_strict_store_failure); return false; } -bool zend_opcache_static_cache_store_locked(zend_string *key, zval *value, zend_long ttl, bool throw_on_failure) +bool zend_opcache_static_cache_store_locked(zend_string *key, zval *value, zend_long ttl, bool throw_on_failure, bool honor_strict_store_failure) { const char *cache_name = zend_opcache_static_cache_active_context()->name; zend_opcache_static_cache_prepared_value prepared; @@ -1771,7 +1777,7 @@ bool zend_opcache_static_cache_store_locked(zend_string *key, zval *value, zend_ * lock contract by dropping the write lock only for preparation and * reacquiring it before returning. */ zend_opcache_static_cache_unlock(); - if (!zend_opcache_static_cache_prepare_value(&prepared, key, value, throw_on_failure, false)) { + if (!zend_opcache_static_cache_prepare_value(&prepared, key, value, throw_on_failure, honor_strict_store_failure, false)) { zend_opcache_static_cache_reacquire_write_lock_or_fail(cache_name); return false; @@ -1783,7 +1789,7 @@ bool zend_opcache_static_cache_store_locked(zend_string *key, zval *value, zend_ return false; } - stored = zend_opcache_static_cache_store_prepared_locked(key, value, &prepared, ttl, throw_on_failure); + stored = zend_opcache_static_cache_store_prepared_locked(key, value, &prepared, ttl, throw_on_failure, honor_strict_store_failure); zend_opcache_static_cache_destroy_prepared_value(&prepared); return stored; @@ -1973,7 +1979,14 @@ bool zend_opcache_static_cache_delete_locked(zend_string *key) return true; } -bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long step, bool decrement, bool insert_if_missing, zend_long *new_value, const char *type_error_message) +bool zend_opcache_static_cache_atomic_update_locked( + zend_string *key, + zend_long step, + bool decrement, + bool insert_if_missing, + zend_long *new_value, + const char *type_error_message, + bool throw_on_error) { zend_opcache_static_cache_header *header; zend_opcache_static_cache_entry *entries, *entry; @@ -1985,7 +1998,7 @@ bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long if (!zend_opcache_static_cache_find_slot_for_write_locked(key, hash, &header, &slot_index, &found) || !found) { if (insert_if_missing) { ZVAL_LONG(&initial_value, decrement ? -step : step); - if (zend_opcache_static_cache_store_locked(key, &initial_value, 0, true)) { + if (zend_opcache_static_cache_store_locked(key, &initial_value, 0, throw_on_error, false)) { *new_value = Z_LVAL(initial_value); return true; @@ -1994,7 +2007,9 @@ bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long return false; } - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Cache key \"%s\" was not found", ZSTR_VAL(key)); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Cache key \"%s\" was not found", ZSTR_VAL(key)); + } return false; } @@ -2002,10 +2017,12 @@ bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long entries = zend_opcache_static_cache_entries(header); entry = &entries[slot_index]; if (entry->value_type != ZEND_OPCACHE_STATIC_CACHE_VALUE_LONG) { - if (entry->value_type > ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" has an unknown type", zend_opcache_static_cache_active_context()->name, ZSTR_VAL(key)); - } else { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", type_error_message); + if (throw_on_error) { + if (entry->value_type > ZEND_OPCACHE_STATIC_CACHE_VALUE_SHARED_GRAPH) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Stored %s value for key \"%s\" has an unknown type", zend_opcache_static_cache_active_context()->name, ZSTR_VAL(key)); + } else { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "%s", type_error_message); + } } return false; diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index 8fde2a204661..7c3a36f89d60 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -425,7 +425,7 @@ void zend_opcache_static_cache_populate_info(zval *return_value); bool zend_opcache_static_cache_rlock(void); bool zend_opcache_static_cache_wlock(void); void zend_opcache_static_cache_unlock(void); -bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key); +bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key, bool throw_on_error); bool zend_opcache_static_cache_try_acquire_entry_lock(zend_string *key, zend_long lease); bool zend_opcache_static_cache_has_entry_lock(zend_string *key); bool zend_opcache_static_cache_release_entry_lock(zend_string *key); @@ -470,6 +470,7 @@ bool zend_opcache_static_cache_prepare_value( zend_string *key, zval *value, bool throw_on_failure, + bool honor_strict_store_failure, bool lock_held); void zend_opcache_static_cache_destroy_prepared_value(zend_opcache_static_cache_prepared_value *prepared); bool zend_opcache_static_cache_store_prepared_locked( @@ -477,12 +478,20 @@ bool zend_opcache_static_cache_store_prepared_locked( zval *value, const zend_opcache_static_cache_prepared_value *prepared, zend_long ttl, - bool throw_on_failure); -bool zend_opcache_static_cache_store_locked(zend_string *key, zval *value, zend_long ttl, bool throw_on_failure); + bool throw_on_failure, + bool honor_strict_store_failure); +bool zend_opcache_static_cache_store_locked(zend_string *key, zval *value, zend_long ttl, bool throw_on_failure, bool honor_strict_store_failure); bool zend_opcache_static_cache_fetch_locked(zend_string *key, zval *return_value, bool throw_if_missing, bool *found_ptr, bool use_request_local_slot); bool zend_opcache_static_cache_exists_locked(zend_string *key); bool zend_opcache_static_cache_delete_locked(zend_string *key); -bool zend_opcache_static_cache_atomic_update_locked(zend_string *key, zend_long step, bool decrement, bool insert_if_missing, zend_long *new_value, const char *type_error_message); +bool zend_opcache_static_cache_atomic_update_locked( + zend_string *key, + zend_long step, + bool decrement, + bool insert_if_missing, + zend_long *new_value, + const char *type_error_message, + bool throw_on_error); void zend_opcache_static_cache_release_request_local_slots(void); void zend_opcache_static_cache_update_mutation_hook_state(void); bool zend_opcache_static_cache_prepare_memo_fetch(zval *value, zend_opcache_static_cache_prepared_value *prepared); diff --git a/ext/opcache/zend_static_cache_statics.c b/ext/opcache/zend_static_cache_statics.c index db2ee77544d8..f9d23e72911c 100644 --- a/ext/opcache/zend_static_cache_statics.c +++ b/ext/opcache/zend_static_cache_statics.c @@ -1303,7 +1303,7 @@ static bool zend_opcache_static_cache_publish_class_blob_handle_locked( } result = zend_opcache_static_cache_class_blob_build_snapshot(handle, &snapshot) && - zend_opcache_static_cache_store_locked(handle->cache_key, &snapshot, handle->ttl, false) + zend_opcache_static_cache_store_locked(handle->cache_key, &snapshot, handle->ttl, false, true) ; zval_ptr_dtor(&snapshot); @@ -2091,7 +2091,7 @@ static void zend_opcache_static_cache_publish_immediate_root(zend_opcache_static published = zend_opcache_static_cache_delete_locked(handle->cache_key); } else { ZVAL_COPY_DEREF(&value_snapshot, slot); - published = zend_opcache_static_cache_store_locked(handle->cache_key, &value_snapshot, handle->ttl, false); + published = zend_opcache_static_cache_store_locked(handle->cache_key, &value_snapshot, handle->ttl, false, true); zval_ptr_dtor(&value_snapshot); } } @@ -2413,7 +2413,7 @@ static void zend_opcache_static_cache_flush_pending(void) ZVAL_COPY_DEREF(&value_snapshot, handle->slot); should_store = Z_TYPE(value_snapshot) != IS_UNDEF; if (should_store) { - published = zend_opcache_static_cache_store_locked(handle->cache_key, &value_snapshot, handle->ttl, false); + published = zend_opcache_static_cache_store_locked(handle->cache_key, &value_snapshot, handle->ttl, false, true); } else { published = zend_opcache_static_cache_delete_locked(handle->cache_key); } @@ -2795,7 +2795,7 @@ static void zend_opcache_static_cache_publish_pinned_static_properties_fast(zend if (Z_TYPE_P(slot) == IS_UNDEF) { published = zend_opcache_static_cache_delete_locked(cache_key); } else { - published = zend_opcache_static_cache_store_locked(cache_key, slot, ttl, false); + published = zend_opcache_static_cache_store_locked(cache_key, slot, ttl, false, true); } } @@ -2935,7 +2935,7 @@ static void zend_opcache_static_cache_publish_context(zend_opcache_static_cache_ if (Z_TYPE_P(slot) == IS_UNDEF) { zend_opcache_static_cache_delete_locked(cache_key); } else { - zend_opcache_static_cache_store_locked(cache_key, slot, ttl, false); + zend_opcache_static_cache_store_locked(cache_key, slot, ttl, false, true); } zend_string_release(cache_key); @@ -2959,7 +2959,7 @@ static void zend_opcache_static_cache_publish_context(zend_opcache_static_cache_ if (Z_TYPE(value_snapshot) == IS_UNDEF) { zend_opcache_static_cache_delete_locked(entry->cache_key); } else { - zend_opcache_static_cache_store_locked(entry->cache_key, &value_snapshot, entry->ttl, false); + zend_opcache_static_cache_store_locked(entry->cache_key, &value_snapshot, entry->ttl, false, true); } zval_ptr_dtor(&value_snapshot); diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 55ca67cfd67a..70179275ca4d 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -2298,7 +2298,7 @@ void zend_opcache_static_cache_unlock(void) zend_opcache_static_cache_unlock_impl(); } -bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key) +bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key, bool throw_on_error) { zend_opcache_static_cache_context *context = zend_opcache_static_cache_active_context(); zend_opcache_static_cache_entry_lock *lock; @@ -2312,13 +2312,17 @@ bool zend_opcache_static_cache_acquire_entry_lock(zend_string *key) } if (!zend_opcache_static_cache_entry_lock_lease_allows_acquire(context, stripe, true)) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); + } return false; } if (!zend_opcache_static_cache_lock_entry_stripe(context, stripe)) { - zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); + if (throw_on_error) { + zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to acquire the %s entry lock", context->name); + } return false; } From 7a512b0f0a05aa15f20b954f51d2c04868b5d90b Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 12:41:47 +0000 Subject: [PATCH 27/29] fix: update func infos --- Zend/Optimizer/zend_func_infos.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 1af82f6cae66..6a5e73f82720 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -288,8 +288,8 @@ static const func_info_t func_infos[] = { F1("mysqli_use_result", MAY_BE_OBJECT|MAY_BE_FALSE), F1("opcache_get_status", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), F1("opcache_get_configuration", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_FALSE), - FN("OPcache\\volatile_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), - FN("OPcache\\pinned_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_NULL), + FN("OPcache\\volatile_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_FALSE), + FN("OPcache\\pinned_fetch_array", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_NULL|MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_FALSE), F1("openssl_x509_parse", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_csr_get_subject", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), F1("openssl_pkey_get_details", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE), From c05ab2560f5157d1b335ad7ca2939e10e17c3167 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Tue, 19 May 2026 15:22:55 +0000 Subject: [PATCH 28/29] fix tests --- ext/opcache/tests/static_cache_preload_001.phpt | 3 +++ .../tests/static_cache_volatile_cache_allocator_002.phpt | 4 ++++ ...e_static_internal_object_method_mutation_fork_001.phpt | 8 ++++++++ 3 files changed, 15 insertions(+) diff --git a/ext/opcache/tests/static_cache_preload_001.phpt b/ext/opcache/tests/static_cache_preload_001.phpt index f9c256d2585a..d3b5a817fecc 100644 --- a/ext/opcache/tests/static_cache_preload_001.phpt +++ b/ext/opcache/tests/static_cache_preload_001.phpt @@ -19,6 +19,9 @@ var_dump(StaticCachePreloadGlobalState::$values); var_dump(StaticCachePreloadGlobalState::next()); var_dump(StaticCachePreloadMethodState::value()); +OPcache\volatile_clear(); +opcache_reset(); + ?> --EXPECT-- array(1) { diff --git a/ext/opcache/tests/static_cache_volatile_cache_allocator_002.phpt b/ext/opcache/tests/static_cache_volatile_cache_allocator_002.phpt index bd1c38159888..f5ccefe1f3c6 100644 --- a/ext/opcache/tests/static_cache_volatile_cache_allocator_002.phpt +++ b/ext/opcache/tests/static_cache_volatile_cache_allocator_002.phpt @@ -9,6 +9,8 @@ opcache.static_cache.volatile_size_mb=8 --FILE-- --EXPECT-- bool(true) diff --git a/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_fork_001.phpt b/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_fork_001.phpt index e8498a3c5c54..a7867f23fc5a 100644 --- a/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_fork_001.phpt +++ b/ext/opcache/tests/static_cache_volatile_static_internal_object_method_mutation_fork_001.phpt @@ -68,6 +68,12 @@ function static_cache_internal_object_wait_for_file(string $path): void } } +function static_cache_internal_object_reset(): void +{ + OPcache\volatile_clear(); + opcache_reset(); +} + function static_cache_internal_object_fork_case(string $state): void { $prefix = sys_get_temp_dir() . '/opcache_volatile_static_internal_object_method_mutation_fork_' . getmypid() . '_' . $state; @@ -152,6 +158,8 @@ foreach (['date', 'array', 'fixed'] as $state) { static_cache_internal_object_fork_case($state); } +static_cache_internal_object_reset(); + ?> --EXPECT-- date=2026-01-02 From 27e359b81784ae68580adc7bb1a6f29e6b0d5390 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Wed, 20 May 2026 06:51:58 +0000 Subject: [PATCH 29/29] refactor and add tests --- Zend/zend_vm_def.h | 3 +- Zend/zend_vm_execute.h | 36 ++-- ...cit_cache_shared_graph_relocation_002.phpt | 123 ++++++++++++++ ..._persistent_cache_atomic_overflow_001.phpt | 98 +++++++++++ ...ructive_mutators_same_key_refetch_001.phpt | 156 ++++++++++++++++++ ext/opcache/zend_opcache_serializer.h | 2 +- ext/opcache/zend_static_cache.c | 133 +++++++++------ ext/opcache/zend_static_cache_entries.c | 59 +++++-- ext/opcache/zend_static_cache_internal.h | 3 +- ext/opcache/zend_static_cache_shared_graph.c | 42 ++++- ext/opcache/zend_static_cache_statics.c | 67 ++++++-- ext/opcache/zend_static_cache_storage.c | 29 +++- ext/spl/spl_fixedarray.c | 6 +- 13 files changed, 633 insertions(+), 124 deletions(-) create mode 100644 ext/opcache/tests/static_cache_explicit_cache_shared_graph_relocation_002.phpt create mode 100644 ext/opcache/tests/static_cache_persistent_cache_atomic_overflow_001.phpt create mode 100644 ext/opcache/tests/static_cache_volatile_cache_destructive_mutators_same_key_refetch_001.phpt diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b67e3fa5169f..6616e33d84f0 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6806,8 +6806,7 @@ ZEND_VM_HANDLER(75, ZEND_UNSET_DIM, VAR|CV, CONST|TMP|CV) zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = GET_OP1_OBJ_ZVAL_PTR_PTR_UNDEF(BP_VAR_UNSET); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 02f850b35001..a0da01cc9509 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -26283,8 +26283,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -28611,8 +28610,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -32590,8 +32588,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -44599,8 +44596,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -48333,8 +48329,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -53608,8 +53603,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_UNSET_DIM_SPE zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -79349,8 +79343,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -81677,8 +81670,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -85656,8 +85648,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_VAR zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); @@ -97665,8 +97656,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -101399,8 +101389,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); @@ -106572,8 +106561,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_UNSET_DIM_SPEC_CV_ zval *offset; zend_ulong hval; zend_string *key; - bool should_flush_array = false; - bool should_publish_tracked_array = false; + bool should_flush_array = false, should_publish_tracked_array = false; SAVE_OPLINE(); container = EX_VAR(opline->op1.var); diff --git a/ext/opcache/tests/static_cache_explicit_cache_shared_graph_relocation_002.phpt b/ext/opcache/tests/static_cache_explicit_cache_shared_graph_relocation_002.phpt new file mode 100644 index 000000000000..df3909ea851f --- /dev/null +++ b/ext/opcache/tests/static_cache_explicit_cache_shared_graph_relocation_002.phpt @@ -0,0 +1,123 @@ +--TEST-- +OPcache explicit volatile and pinned caches rebase shared graph direct arrays during relocation +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=8 +opcache.static_cache.pinned_size_mb=8 +--FILE-- + $i, + 'text' => str_repeat($label, 48), + 'nested' => [$i * $multiplier, $i * $multiplier + 1], + ]; + } + + return [ + 'object' => new SharedGraphRelocationNode('node-' . $label, $rows), + 'plain' => [ + 'label' => $label, + 'tail' => str_repeat($label, 256), + ], + ]; +} + +function cache_clear_for_relocation(string $backend): void +{ + $function = 'OPcache\\' . $backend . '_clear'; + $function(); +} + +function cache_store_for_relocation(string $backend, string $key, mixed $value): bool +{ + $function = 'OPcache\\' . $backend . '_store'; + + return $function($key, $value); +} + +function cache_fetch_for_relocation(string $backend, string $key): mixed +{ + $function = 'OPcache\\' . $backend . '_fetch'; + + return $function($key); +} + +function cache_delete_for_relocation(string $backend, string $key): void +{ + $function = 'OPcache\\' . $backend . '_delete'; + $function($key); +} + +function run_shared_graph_relocation(string $backend, string $label): void +{ + echo "-- {$backend} --\n"; + + cache_clear_for_relocation($backend); + + $prefix = $backend . '_shared_graph_relocation_'; + var_dump(cache_store_for_relocation($backend, $prefix . 'first', str_repeat('A', 1200000))); + var_dump(cache_store_for_relocation($backend, $prefix . 'graph', build_shared_graph_relocation_payload($label, 5))); + var_dump(cache_store_for_relocation($backend, $prefix . 'third', str_repeat('C', 1200000))); + + cache_delete_for_relocation($backend, $prefix . 'first'); + + var_dump(cache_store_for_relocation($backend, $prefix . 'merged', str_repeat('M', 2400000))); + + $graph = cache_fetch_for_relocation($backend, $prefix . 'graph'); + var_dump($graph['object'] instanceof SharedGraphRelocationNode); + var_dump($graph['object']->name); + var_dump($graph['object']->rows[123]['text']); + var_dump($graph['object']->rows[123]['nested'][1]); + var_dump($graph['plain']['label']); + var_dump(strlen($graph['plain']['tail'])); + var_dump(strlen(cache_fetch_for_relocation($backend, $prefix . 'merged'))); + var_dump(strlen(cache_fetch_for_relocation($backend, $prefix . 'third'))); +} + +run_shared_graph_relocation('volatile', 'V'); +run_shared_graph_relocation('pinned', 'P'); + +?> +--EXPECT-- +-- volatile -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +string(6) "node-V" +string(48) "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" +int(616) +string(1) "V" +int(256) +int(2400000) +int(1200000) +-- pinned -- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +string(6) "node-P" +string(48) "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" +int(616) +string(1) "P" +int(256) +int(2400000) +int(1200000) diff --git a/ext/opcache/tests/static_cache_persistent_cache_atomic_overflow_001.phpt b/ext/opcache/tests/static_cache_persistent_cache_atomic_overflow_001.phpt new file mode 100644 index 000000000000..6537de7aa75d --- /dev/null +++ b/ext/opcache/tests/static_cache_persistent_cache_atomic_overflow_001.phpt @@ -0,0 +1,98 @@ +--TEST-- +OPcache pinned atomic operations warn and wrap on integer overflow +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.pinned_size_mb=32 +--FILE-- + OPcache\pinned_atomic_increment($maxKey), PHP_INT_MIN); +echo "max wrapped no throw: "; +var_dump(OPcache\pinned_fetch($maxKey) === PHP_INT_MIN); + +var_dump(OPcache\pinned_store($maxKey, PHP_INT_MAX)); +show_result('increment max throw flag', static fn () => OPcache\pinned_atomic_increment($maxKey, 1, true), PHP_INT_MIN); +echo "max wrapped throw flag: "; +var_dump(OPcache\pinned_fetch($maxKey) === PHP_INT_MIN); + +var_dump(OPcache\pinned_store($minKey, PHP_INT_MIN)); +show_result('decrement min no throw', static fn () => OPcache\pinned_atomic_decrement($minKey), PHP_INT_MAX); +echo "min wrapped no throw: "; +var_dump(OPcache\pinned_fetch($minKey) === PHP_INT_MAX); + +var_dump(OPcache\pinned_store($minKey, PHP_INT_MIN)); +show_result('decrement min throw flag', static fn () => OPcache\pinned_atomic_decrement($minKey, 1, true), PHP_INT_MAX); +echo "min wrapped throw flag: "; +var_dump(OPcache\pinned_fetch($minKey) === PHP_INT_MAX); + +show_result('decrement missing min step', static fn () => OPcache\pinned_atomic_decrement($missingKey, PHP_INT_MIN), PHP_INT_MIN); +echo "missing wrapped: "; +var_dump(OPcache\pinned_fetch($missingKey) === PHP_INT_MIN); + +?> +--EXPECT-- +bool(true) +-- increment max no throw -- +warning severity: bool(true) +warning message: OPcache\pinned_atomic_increment(): Integer overflow occurred; result wrapped around +handler fetch: bool(true) +result: bool(true) +max wrapped no throw: bool(true) +bool(true) +-- increment max throw flag -- +warning severity: bool(true) +warning message: OPcache\pinned_atomic_increment(): Integer overflow occurred; result wrapped around +handler fetch: bool(true) +result: bool(true) +max wrapped throw flag: bool(true) +bool(true) +-- decrement min no throw -- +warning severity: bool(true) +warning message: OPcache\pinned_atomic_decrement(): Integer overflow occurred; result wrapped around +handler fetch: bool(true) +result: bool(true) +min wrapped no throw: bool(true) +bool(true) +-- decrement min throw flag -- +warning severity: bool(true) +warning message: OPcache\pinned_atomic_decrement(): Integer overflow occurred; result wrapped around +handler fetch: bool(true) +result: bool(true) +min wrapped throw flag: bool(true) +-- decrement missing min step -- +warning severity: bool(true) +warning message: OPcache\pinned_atomic_decrement(): Integer overflow occurred; result wrapped around +handler fetch: bool(true) +result: bool(true) +missing wrapped: bool(true) diff --git a/ext/opcache/tests/static_cache_volatile_cache_destructive_mutators_same_key_refetch_001.phpt b/ext/opcache/tests/static_cache_volatile_cache_destructive_mutators_same_key_refetch_001.phpt new file mode 100644 index 000000000000..35c03b85c227 --- /dev/null +++ b/ext/opcache/tests/static_cache_volatile_cache_destructive_mutators_same_key_refetch_001.phpt @@ -0,0 +1,156 @@ +--TEST-- +OPcache volatile cache destructive mutators allow same-key replacement while old shared graph is referenced +--EXTENSIONS-- +opcache +pcntl +--SKIPIF-- + +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.static_cache.volatile_size_mb=32 +--FILE-- + $i, + 'text' => str_repeat($prefix, 64), + 'nested' => ['value' => $i * $multiplier], + ]; + } + + return $rows; +} + +function build_same_key_payload(string $prefix, int $multiplier): array +{ + return [ + 'name' => 'payload-' . $prefix, + 'rows' => build_same_key_rows($prefix, $multiplier), + ]; +} + +function wait_for_same_key_file(string $path): void +{ + $deadline = microtime(true) + 5.0; + while (!file_exists($path)) { + if (microtime(true) >= $deadline) { + throw new RuntimeException("timed out waiting for {$path}"); + } + usleep(1000); + } +} + +function run_same_key_refetch_case(string $operation): void +{ + $key = 'destructive_mutator_same_key_refetch_' . $operation . '_' . getmypid(); + $prefix = sys_get_temp_dir() . '/opcache_destructive_mutator_same_key_refetch_' . getmypid() . '_' . $operation; + $readyFile = $prefix . '.ready'; + $doneFile = $prefix . '.done'; + $resultFile = $prefix . '.result'; + @unlink($readyFile); + @unlink($doneFile); + @unlink($resultFile); + + OPcache\volatile_clear(); + if (!OPcache\volatile_store($key, build_same_key_payload('A', 3))) { + throw new RuntimeException('initial store failed'); + } + + $pid = pcntl_fork(); + if ($pid === -1) { + throw new RuntimeException('pcntl_fork() failed'); + } + + if ($pid === 0) { + $fetched = OPcache\volatile_fetch($key); + file_put_contents($readyFile, 'ready'); + wait_for_same_key_file($doneFile); + + $before = $fetched['rows'][123]['text']; + $fetched['rows'][123]['text'] = 'changed old after ' . $operation; + $after = $fetched['rows'][123]['text']; + $oldNested = $fetched['rows'][123]['nested']['value']; + $refetched = OPcache\volatile_fetch($key, 'MISS'); + + file_put_contents( + $resultFile, + $before . "\n" . + $after . "\n" . + $oldNested . "\n" . + $refetched['rows'][123]['text'] . "\n" . + $refetched['rows'][123]['nested']['value'] + ); + exit(0); + } + + wait_for_same_key_file($readyFile); + switch ($operation) { + case 'delete': + if (!OPcache\volatile_delete($key)) { + throw new RuntimeException('delete failed'); + } + break; + case 'clear': + if (!OPcache\volatile_clear()) { + throw new RuntimeException('clear failed'); + } + break; + default: + throw new RuntimeException('unknown operation'); + } + + if (!OPcache\volatile_store($key, build_same_key_payload('B', 7))) { + throw new RuntimeException('replacement store failed'); + } + $parentRefetched = OPcache\volatile_fetch($key); + + file_put_contents($doneFile, 'done'); + pcntl_waitpid($pid, $status); + + echo $operation, "\n"; + echo $parentRefetched['rows'][123]['text'], "\n"; + echo $parentRefetched['rows'][123]['nested']['value'], "\n"; + echo file_get_contents($resultFile), "\n"; + + @unlink($readyFile); + @unlink($doneFile); + @unlink($resultFile); +} + +run_same_key_refetch_case('delete'); +run_same_key_refetch_case('clear'); + +?> +--EXPECT-- +delete +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +861 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +changed old after delete +369 +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +861 +clear +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +861 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +changed old after clear +369 +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +861 +--CLEAN-- + diff --git a/ext/opcache/zend_opcache_serializer.h b/ext/opcache/zend_opcache_serializer.h index c14d8487623f..059028b8fd03 100644 --- a/ext/opcache/zend_opcache_serializer.h +++ b/ext/opcache/zend_opcache_serializer.h @@ -797,8 +797,8 @@ static zend_always_inline bool zend_opcache_serializer_try_encode_safe_direct_ob zend_opcache_serializer_wbuf_t *wb, const zval *zv, size_t hdr_offset, zend_class_entry *ce, zend_string *class_name, uint32_t name_len) { - zend_class_entry *base_ce = NULL; zend_opcache_static_cache_safe_direct_state_copy_func_t copy_func; + zend_class_entry *base_ce = NULL; bool ok; copy_func = zend_opcache_static_cache_safe_direct_copy_func(ce, &base_ce); diff --git a/ext/opcache/zend_static_cache.c b/ext/opcache/zend_static_cache.c index 6e2bad952fe1..ca2b15e3c6c7 100644 --- a/ext/opcache/zend_static_cache.c +++ b/ext/opcache/zend_static_cache.c @@ -654,6 +654,11 @@ static zend_always_inline bool zend_opcache_static_cache_acquire_write_lock(bool return true; } +static zend_always_inline void zend_opcache_static_cache_warn_atomic_overflow(const char *function_name) +{ + zend_error(E_WARNING, "%s(): Integer overflow occurred; result wrapped around", function_name); +} + static zend_always_inline bool zend_opcache_static_cache_begin_entry_mutation(zend_string *key, bool *release_entry_lock, bool throw_on_error) { ZEND_ASSERT(release_entry_lock != NULL); @@ -942,11 +947,6 @@ static zend_always_inline void zend_opcache_static_cache_disable_subsystem(const zend_opcache_static_cache_restore_context(previous_context); } -zend_result zend_opcache_register_functions(int module_type) -{ - return zend_register_functions(NULL, ext_functions, NULL, module_type); -} - static void zend_opcache_static_cache_startup(void) { const char *failure_reason; @@ -1082,24 +1082,6 @@ static zend_result zend_opcache_static_cache_rinit(void) return SUCCESS; } -zend_result zend_opcache_static_cache_rshutdown(void) -{ - zend_opcache_static_cache_clear_lookup_caches(); - - EG(static_cache_class_access_active) = false; - EG(tracked_mutation_hooks_active) = false; - - zend_opcache_static_cache_request_shutdown(); - zend_opcache_static_cache_release_request_entry_locks(); - zend_opcache_static_cache_release_request_local_slots(); - zend_opcache_static_cache_release_request_shared_graph_refs(); - - EG(static_cache_class_access_active) = false; - EG(tracked_mutation_hooks_active) = false; - - return SUCCESS; -} - static void zend_opcache_static_cache_invalidate_script(zend_persistent_script *persistent_script) { zend_opcache_static_cache_invalidate_script_context(&zend_opcache_static_cache_pinned_context_state, persistent_script); @@ -1192,6 +1174,29 @@ const char *zend_opcache_static_cache_pinned_failure_reason(void) return zend_opcache_static_cache_context_runtime(&zend_opcache_static_cache_pinned_context_state)->failure_reason; } +zend_result zend_opcache_static_cache_rshutdown(void) +{ + zend_opcache_static_cache_clear_lookup_caches(); + + EG(static_cache_class_access_active) = false; + EG(tracked_mutation_hooks_active) = false; + + zend_opcache_static_cache_request_shutdown(); + zend_opcache_static_cache_release_request_entry_locks(); + zend_opcache_static_cache_release_request_local_slots(); + zend_opcache_static_cache_release_request_shared_graph_refs(); + + EG(static_cache_class_access_active) = false; + EG(tracked_mutation_hooks_active) = false; + + return SUCCESS; +} + +zend_result zend_opcache_register_functions(int module_type) +{ + return zend_register_functions(NULL, ext_functions, NULL, module_type); +} + ZEND_METHOD(OPcache_VolatileStatic, __construct) { zend_long ttl = 0; @@ -1225,8 +1230,7 @@ ZEND_FUNCTION(OPcache_volatile_store) zend_string *key; zend_long ttl = 0; zval *value; - bool throw_on_error = false; - bool stored; + bool throw_on_error = false, stored; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR(key) @@ -1251,6 +1255,7 @@ ZEND_FUNCTION(OPcache_volatile_store) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); if (!zend_opcache_static_cache_store_backend_available(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -1279,8 +1284,7 @@ ZEND_FUNCTION(OPcache_volatile_store_array) zend_long ttl = 0; zval *value; HashTable *values; - bool throw_on_error = false; - bool stored; + bool throw_on_error = false, stored; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ARRAY_HT(values) @@ -1300,6 +1304,7 @@ ZEND_FUNCTION(OPcache_volatile_store_array) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_volatile_context_state); if (!zend_opcache_static_cache_store_backend_available(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -1391,8 +1396,7 @@ ZEND_FUNCTION(OPcache_volatile_fetch_array) ZEND_FUNCTION(OPcache_volatile_exists) { zend_string *key; - bool throw_on_error = false; - bool exists; + bool throw_on_error = false, exists; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) @@ -1419,8 +1423,7 @@ ZEND_FUNCTION(OPcache_volatile_lock) { zend_string *key; zend_long lease = 0; - bool throw_on_error = false; - bool locked; + bool throw_on_error = false, locked; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) @@ -1451,8 +1454,7 @@ ZEND_FUNCTION(OPcache_volatile_lock) ZEND_FUNCTION(OPcache_volatile_unlock) { zend_string *key; - bool throw_on_error = false; - bool unlocked; + bool throw_on_error = false, unlocked; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) @@ -1528,6 +1530,7 @@ ZEND_FUNCTION(OPcache_volatile_delete_array) if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + if (EG(exception)) { RETURN_THROWS(); } @@ -1538,6 +1541,7 @@ ZEND_FUNCTION(OPcache_volatile_delete_array) for (index = 0; index < key_count; index++) { if (!zend_opcache_static_cache_explicit_delete_prevalidated(prepared_keys[index], throw_on_error)) { zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + if (EG(exception)) { RETURN_THROWS(); } @@ -1547,6 +1551,7 @@ ZEND_FUNCTION(OPcache_volatile_delete_array) } zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + RETURN_TRUE; } @@ -1571,6 +1576,7 @@ ZEND_FUNCTION(OPcache_volatile_clear) if (throw_on_error && !EG(exception)) { zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the volatile cache"); } + if (EG(exception)) { RETURN_THROWS(); } @@ -1579,6 +1585,7 @@ ZEND_FUNCTION(OPcache_volatile_clear) } zend_opcache_static_cache_mark_publish_skipped(&zend_opcache_static_cache_volatile_context_state); + RETURN_TRUE; } @@ -1604,8 +1611,7 @@ ZEND_FUNCTION(OPcache_pinned_store) zend_opcache_static_cache_context *previous_context; zend_string *key; zval *value; - bool throw_on_error = false; - bool stored; + bool throw_on_error = false, stored; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_STR(key) @@ -1625,6 +1631,7 @@ ZEND_FUNCTION(OPcache_pinned_store) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_store_backend_available(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -1652,8 +1659,7 @@ ZEND_FUNCTION(OPcache_pinned_store_array) zend_string *key; zval *value; HashTable *values; - bool throw_on_error = false; - bool stored; + bool throw_on_error = false, stored; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY_HT(values) @@ -1747,7 +1753,10 @@ ZEND_FUNCTION(OPcache_pinned_fetch_array) default_value = &default_null; } - if (zend_opcache_static_cache_fetch_array_api(&zend_opcache_static_cache_pinned_context_state, keys, default_value, return_value, throw_on_error) == FAILURE) { + if (zend_opcache_static_cache_fetch_array_api( + &zend_opcache_static_cache_pinned_context_state, keys, default_value, return_value, throw_on_error + ) == FAILURE + ) { if (EG(exception)) { RETURN_THROWS(); } @@ -1759,8 +1768,7 @@ ZEND_FUNCTION(OPcache_pinned_fetch_array) ZEND_FUNCTION(OPcache_pinned_exists) { zend_string *key; - bool throw_on_error = false; - bool exists; + bool throw_on_error = false, exists; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) @@ -1787,8 +1795,7 @@ ZEND_FUNCTION(OPcache_pinned_lock) { zend_string *key; zend_long lease = 0; - bool throw_on_error = false; - bool locked; + bool throw_on_error = false, locked; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) @@ -1819,8 +1826,7 @@ ZEND_FUNCTION(OPcache_pinned_lock) ZEND_FUNCTION(OPcache_pinned_unlock) { zend_string *key; - bool throw_on_error = false; - bool unlocked; + bool throw_on_error = false, unlocked; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(key) @@ -1862,6 +1868,7 @@ ZEND_FUNCTION(OPcache_pinned_delete) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -1871,13 +1878,16 @@ ZEND_FUNCTION(OPcache_pinned_delete) if (!zend_opcache_static_cache_explicit_delete_or_class_prevalidated(key_or_class, throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } RETURN_FALSE; } + zend_opcache_static_cache_restore_context(previous_context); + RETURN_TRUE; } @@ -1914,6 +1924,7 @@ ZEND_FUNCTION(OPcache_pinned_delete_array) if (!zend_opcache_static_cache_explicit_delete_prevalidated(prepared_keys[index], throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + if (EG(exception)) { RETURN_THROWS(); } @@ -1924,6 +1935,7 @@ ZEND_FUNCTION(OPcache_pinned_delete_array) zend_opcache_static_cache_restore_context(previous_context); zend_opcache_static_cache_release_key_list(prepared_keys, key_count); + RETURN_TRUE; } @@ -1952,6 +1964,7 @@ ZEND_FUNCTION(OPcache_pinned_clear) if (throw_on_error && !EG(exception)) { zend_throw_exception_ex(zend_opcache_static_cache_exception_ce, 0, "Unable to clear the pinned cache"); } + if (EG(exception)) { RETURN_THROWS(); } @@ -1961,6 +1974,7 @@ ZEND_FUNCTION(OPcache_pinned_clear) zend_opcache_static_cache_mark_publish_skipped(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_restore_context(previous_context); + RETURN_TRUE; } @@ -1969,8 +1983,7 @@ ZEND_FUNCTION(OPcache_pinned_atomic_increment) zend_opcache_static_cache_context *previous_context; zend_long step = 1, new_value; zend_string *key; - bool throw_on_error = false; - bool release_entry_lock = false; + bool throw_on_error = false, release_entry_lock = false, is_overflow = false; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) @@ -1986,6 +1999,7 @@ ZEND_FUNCTION(OPcache_pinned_atomic_increment) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -1995,6 +2009,7 @@ ZEND_FUNCTION(OPcache_pinned_atomic_increment) if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock, throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -2006,7 +2021,9 @@ ZEND_FUNCTION(OPcache_pinned_atomic_increment) if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); } + zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -2014,12 +2031,15 @@ ZEND_FUNCTION(OPcache_pinned_atomic_increment) RETURN_FALSE; } - if (!zend_opcache_static_cache_atomic_update_locked(key, step, false, true, &new_value, "Atomic increment requires an integer value", throw_on_error)) { + if (!zend_opcache_static_cache_atomic_update_locked(key, step, false, true, &new_value, &is_overflow, "Atomic increment requires an integer value", throw_on_error)) { zend_opcache_static_cache_unlock(); + if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); } + zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -2031,6 +2051,10 @@ ZEND_FUNCTION(OPcache_pinned_atomic_increment) zend_opcache_static_cache_unlock(); zend_opcache_static_cache_finish_entry_mutation(key, release_entry_lock, true); zend_opcache_static_cache_restore_context(previous_context); + + if (is_overflow) { + zend_opcache_static_cache_warn_atomic_overflow("OPcache\\pinned_atomic_increment"); + } } ZEND_FUNCTION(OPcache_pinned_atomic_decrement) @@ -2038,8 +2062,7 @@ ZEND_FUNCTION(OPcache_pinned_atomic_decrement) zend_opcache_static_cache_context *previous_context; zend_string *key; zend_long step = 1, new_value; - bool throw_on_error = false; - bool release_entry_lock = false; + bool throw_on_error = false, release_entry_lock = false, is_overflow = false; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(key) @@ -2055,6 +2078,7 @@ ZEND_FUNCTION(OPcache_pinned_atomic_decrement) previous_context = zend_opcache_static_cache_activate_context(&zend_opcache_static_cache_pinned_context_state); if (!zend_opcache_static_cache_validate_available_write(throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -2064,6 +2088,7 @@ ZEND_FUNCTION(OPcache_pinned_atomic_decrement) if (!zend_opcache_static_cache_begin_entry_mutation(key, &release_entry_lock, throw_on_error)) { zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -2075,7 +2100,9 @@ ZEND_FUNCTION(OPcache_pinned_atomic_decrement) if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); } + zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -2083,12 +2110,14 @@ ZEND_FUNCTION(OPcache_pinned_atomic_decrement) RETURN_FALSE; } - if (!zend_opcache_static_cache_atomic_update_locked(key, step, true, true, &new_value, "Atomic decrement requires an integer value", throw_on_error)) { + if (!zend_opcache_static_cache_atomic_update_locked(key, step, true, true, &new_value, &is_overflow, "Atomic decrement requires an integer value", throw_on_error)) { zend_opcache_static_cache_unlock(); if (release_entry_lock) { zend_opcache_static_cache_release_entry_lock(key); } + zend_opcache_static_cache_restore_context(previous_context); + if (EG(exception)) { RETURN_THROWS(); } @@ -2100,6 +2129,10 @@ ZEND_FUNCTION(OPcache_pinned_atomic_decrement) zend_opcache_static_cache_unlock(); zend_opcache_static_cache_finish_entry_mutation(key, release_entry_lock, true); zend_opcache_static_cache_restore_context(previous_context); + + if (is_overflow) { + zend_opcache_static_cache_warn_atomic_overflow("OPcache\\pinned_atomic_decrement"); + } } ZEND_FUNCTION(OPcache_pinned_cache_info) diff --git a/ext/opcache/zend_static_cache_entries.c b/ext/opcache/zend_static_cache_entries.c index 21b8db4160dd..e6f725610ea2 100644 --- a/ext/opcache/zend_static_cache_entries.c +++ b/ext/opcache/zend_static_cache_entries.c @@ -136,6 +136,7 @@ static void zend_opcache_static_cache_request_local_slot_dtor(zval *slot_zv) if (!Z_ISUNDEF(slot->value)) { zval_ptr_dtor(&slot->value); } + efree(slot); } @@ -179,11 +180,9 @@ static bool zend_opcache_static_cache_value_needs_request_local_clone(zval *valu if (Z_ISREF_P(value)) { return true; - } - if (Z_TYPE_P(value) == IS_OBJECT) { + } else if (Z_TYPE_P(value) == IS_OBJECT) { return true; - } - if (Z_TYPE_P(value) != IS_ARRAY) { + } else if (Z_TYPE_P(value) != IS_ARRAY) { return false; } @@ -286,9 +285,10 @@ static bool zend_opcache_static_cache_clone_request_local_reference( zend_reference *src_ref) { zend_ulong key = (zend_ulong) (uintptr_t) src_ref; - zend_reference *new_ref = zend_hash_index_find_ptr(&context->references, key); + zend_reference *new_ref; zval inner; + new_ref = zend_hash_index_find_ptr(&context->references, key); if (new_ref != NULL) { GC_ADDREF(new_ref); ZVAL_REF(dst, new_ref); @@ -335,6 +335,7 @@ static bool zend_opcache_static_cache_clone_request_local_object_members( zval_ptr_dtor(dst); ZVAL_COPY_VALUE(dst, &new_prop); Z_PROP_FLAG_P(dst) = Z_PROP_FLAG_P(src); + src++; dst++; } while (src != end); @@ -350,7 +351,8 @@ static bool zend_opcache_static_cache_clone_request_local_object_members( } HT_FLAGS(new_object->properties) |= - HT_FLAGS(old_object->properties) & HASH_FLAG_HAS_EMPTY_IND; + HT_FLAGS(old_object->properties) & HASH_FLAG_HAS_EMPTY_IND + ; ZEND_HASH_MAP_FOREACH_KEY_VAL(old_object->properties, num_key, key, prop) { if (Z_TYPE_P(prop) == IS_INDIRECT) { @@ -955,6 +957,7 @@ static bool zend_opcache_static_cache_find_unstorable_value( if (zend_hash_index_exists(seen_arrays, key)) { return false; } + zend_hash_index_add_empty_element(seen_arrays, key); ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value), element) { @@ -1979,12 +1982,35 @@ bool zend_opcache_static_cache_delete_locked(zend_string *key) return true; } +static zend_always_inline bool zend_opcache_static_cache_long_add_wrapped( + zend_long lhs, + zend_long rhs, + zend_long *result) +{ + *result = (zend_long) ((zend_ulong) lhs + (zend_ulong) rhs); + + return (rhs > 0 && lhs > ZEND_LONG_MAX - rhs) || + (rhs < 0 && lhs < ZEND_LONG_MIN - rhs); +} + +static zend_always_inline bool zend_opcache_static_cache_long_sub_wrapped( + zend_long lhs, + zend_long rhs, + zend_long *result) +{ + *result = (zend_long) ((zend_ulong) lhs - (zend_ulong) rhs); + + return (rhs > 0 && lhs < ZEND_LONG_MIN + rhs) || + (rhs < 0 && lhs > ZEND_LONG_MAX + rhs); +} + bool zend_opcache_static_cache_atomic_update_locked( zend_string *key, zend_long step, bool decrement, bool insert_if_missing, zend_long *new_value, + bool *is_overflow, const char *type_error_message, bool throw_on_error) { @@ -1992,13 +2018,20 @@ bool zend_opcache_static_cache_atomic_update_locked( zend_opcache_static_cache_entry *entries, *entry; zend_ulong hash = zend_string_hash_val(key); zval initial_value = {0}; + zend_long result; uint32_t slot_index; - bool found; + bool found, result_is_overflow; + + *is_overflow = false; if (!zend_opcache_static_cache_find_slot_for_write_locked(key, hash, &header, &slot_index, &found) || !found) { if (insert_if_missing) { - ZVAL_LONG(&initial_value, decrement ? -step : step); + result_is_overflow = decrement + ? zend_opcache_static_cache_long_sub_wrapped(0, step, &result) + : zend_opcache_static_cache_long_add_wrapped(0, step, &result); + ZVAL_LONG(&initial_value, result); if (zend_opcache_static_cache_store_locked(key, &initial_value, 0, throw_on_error, false)) { + *is_overflow = result_is_overflow; *new_value = Z_LVAL(initial_value); return true; @@ -2028,11 +2061,11 @@ bool zend_opcache_static_cache_atomic_update_locked( return false; } - if (decrement) { - entry->long_value -= step; - } else { - entry->long_value += step; - } + result_is_overflow = decrement + ? zend_opcache_static_cache_long_sub_wrapped(entry->long_value, step, &result) + : zend_opcache_static_cache_long_add_wrapped(entry->long_value, step, &result); + entry->long_value = result; + *is_overflow = result_is_overflow; zend_opcache_static_cache_bump_mutation_epoch_locked(header); *new_value = entry->long_value; diff --git a/ext/opcache/zend_static_cache_internal.h b/ext/opcache/zend_static_cache_internal.h index 7c3a36f89d60..97fe04a93634 100644 --- a/ext/opcache/zend_static_cache_internal.h +++ b/ext/opcache/zend_static_cache_internal.h @@ -41,7 +41,7 @@ #include "SAPI.h" #define ZEND_OPCACHE_STATIC_CACHE_MAGIC 0xCAC17E01U -#define ZEND_OPCACHE_STATIC_CACHE_VERSION 2U +#define ZEND_OPCACHE_STATIC_CACHE_VERSION 1U #define ZEND_OPCACHE_STATIC_CACHE_MIN_CAPACITY 127U #define ZEND_OPCACHE_STATIC_CACHE_MAX_CAPACITY 65521U #define ZEND_OPCACHE_STATIC_CACHE_SLOT_BYTES 256U @@ -490,6 +490,7 @@ bool zend_opcache_static_cache_atomic_update_locked( bool decrement, bool insert_if_missing, zend_long *new_value, + bool *is_overflow, const char *type_error_message, bool throw_on_error); void zend_opcache_static_cache_release_request_local_slots(void); diff --git a/ext/opcache/zend_static_cache_shared_graph.c b/ext/opcache/zend_static_cache_shared_graph.c index fd5c92c67bdf..17c3ac568cac 100644 --- a/ext/opcache/zend_static_cache_shared_graph.c +++ b/ext/opcache/zend_static_cache_shared_graph.c @@ -405,7 +405,12 @@ static bool zend_opcache_static_cache_shared_graph_calc_value( return false; } - source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + source_value = + Z_TYPE_P(property_value) == IS_INDIRECT + ? Z_INDIRECT_P(property_value) + : property_value + ; + if (!zend_opcache_static_cache_shared_graph_calc_value(context, source_value)) { zend_release_properties(properties); return false; @@ -831,7 +836,12 @@ static bool zend_opcache_static_cache_shared_graph_copy_property_value( return false; } - source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + source_value = + Z_TYPE_P(property_value) == IS_INDIRECT + ? Z_INDIRECT_P(property_value) + : property_value + ; + if (!zend_opcache_static_cache_shared_graph_copy_property_value( context, source_value, @@ -1169,6 +1179,7 @@ static bool zend_opcache_static_cache_free_retired_shared_graphs(void) } previous_context = zend_opcache_static_cache_activate_context(ref->context); + if (zend_opcache_static_cache_wlock()) { if (zend_opcache_static_cache_header_is_initialized_locked()) { zend_opcache_static_cache_free_locked(ref->payload_offset); @@ -1268,8 +1279,8 @@ bool zend_opcache_static_cache_build_shared_graph_in_place( result = zend_opcache_static_cache_shared_graph_copy_alloc(©_context, sizeof(*header), &header_offset) && header_offset == 0; if (result) { if (Z_TYPE_P(value) == IS_OBJECT) { - result = zend_opcache_static_cache_shared_graph_copy_property_value(©_context, value, &root_value) - && root_value.type == ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT + result = zend_opcache_static_cache_shared_graph_copy_property_value(©_context, value, &root_value) && + root_value.type == ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_OBJECT ; } else if (Z_TYPE_P(value) == IS_ARRAY) { result = zend_opcache_static_cache_shared_graph_copy_property_value(©_context, value, &root_value) && @@ -1297,7 +1308,9 @@ bool zend_opcache_static_cache_build_shared_graph_in_place( header->version = ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VERSION; header->root_offset = root_offset; header->root_type = root_type; + ZEND_ATOMIC_INT_INIT(&header->ref_state, 0); + if (graph_len != NULL) { *graph_len = copy_context.position; } @@ -1322,6 +1335,7 @@ bool zend_opcache_static_cache_shared_graph_copy_fits_buffer( } target_padding = zend_opcache_static_cache_shared_graph_alignment_padding(target_buffer); + return target_padding <= target_buffer_len && source_graph_len <= target_buffer_len - target_padding; } @@ -1460,7 +1474,9 @@ static bool zend_opcache_static_cache_shared_graph_rebase_direct_zval( len, delta ); + Z_ARR_P(value) = array; + if (!zend_opcache_static_cache_shared_graph_pointer_in_range(array, new_base, len)) { return true; } @@ -1500,6 +1516,7 @@ static bool zend_opcache_static_cache_shared_graph_rebase_direct_array( if (zend_hash_index_exists(seen_arrays, key)) { return true; } + if (zend_hash_index_add_empty_element(seen_arrays, key) == NULL) { return false; } @@ -1507,6 +1524,7 @@ static bool zend_opcache_static_cache_shared_graph_rebase_direct_array( data = HT_GET_DATA_ADDR(array); data = zend_opcache_static_cache_shared_graph_rebase_pointer(data, old_base, len, delta); HT_SET_DATA_ADDR(array, data); + if (!zend_opcache_static_cache_shared_graph_pointer_in_range(data, new_base, len)) { return false; } @@ -1520,7 +1538,8 @@ static bool zend_opcache_static_cache_shared_graph_rebase_direct_array( new_base, len, delta, - seen_arrays) + seen_arrays + ) ) { return false; } @@ -1547,7 +1566,8 @@ static bool zend_opcache_static_cache_shared_graph_rebase_direct_array( new_base, len, delta, - seen_arrays) + seen_arrays + ) ) { return false; } @@ -1580,6 +1600,7 @@ static bool zend_opcache_static_cache_shared_graph_rebase_graph_value( } array = (zend_array *) (void *) (buffer + (uint32_t) value->payload.offset); + return zend_opcache_static_cache_shared_graph_rebase_direct_array( array, old_base, @@ -1591,6 +1612,7 @@ static bool zend_opcache_static_cache_shared_graph_rebase_graph_value( case ZEND_OPCACHE_STATIC_CACHE_SHARED_GRAPH_VALUE_DYNAMIC_ARRAY: graph_array = (const zend_opcache_static_cache_shared_graph_array *) (buffer + (uint32_t) value->payload.offset); graph_elements = (const zend_opcache_static_cache_shared_graph_array_element *) (buffer + graph_array->elements_offset); + for (index = 0; index < graph_array->count; index++) { if (!zend_opcache_static_cache_shared_graph_rebase_graph_value( buffer, @@ -1599,7 +1621,8 @@ static bool zend_opcache_static_cache_shared_graph_rebase_graph_value( new_base, len, delta, - seen_arrays) + seen_arrays + ) ) { return false; } @@ -1616,7 +1639,8 @@ static bool zend_opcache_static_cache_shared_graph_rebase_graph_value( new_base, len, delta, - seen_arrays) + seen_arrays + ) ) { return false; } @@ -1720,6 +1744,7 @@ bool zend_opcache_static_cache_shared_graph_rebase_moved_payload_locked(uint32_t result = false; break; } + zend_hash_destroy(&seen_arrays); return result; @@ -1882,6 +1907,7 @@ bool zend_opcache_static_cache_release_request_shared_graph_refs(void) } previous_context = zend_opcache_static_cache_activate_context(ref->context); + if (zend_opcache_static_cache_wlock()) { if (zend_opcache_static_cache_header_is_initialized_locked() && ref->payload_offset != 0 diff --git a/ext/opcache/zend_static_cache_statics.c b/ext/opcache/zend_static_cache_statics.c index f9d23e72911c..bf71bdc57edc 100644 --- a/ext/opcache/zend_static_cache_statics.c +++ b/ext/opcache/zend_static_cache_statics.c @@ -40,6 +40,12 @@ static ZEND_EXT_TLS HashTable *zend_opcache_static_cache_prepare_memo_objects = static ZEND_EXT_TLS bool zend_opcache_static_cache_has_prepare_memo_arrays = false; static ZEND_EXT_TLS bool zend_opcache_static_cache_has_prepare_memo_objects = false; +static void zend_opcache_static_cache_capture_decoded_reachable_value_ex( + zval *value, + HashTable *seen_arrays, + HashTable *seen_objects +); + static zend_always_inline HashTable *zend_opcache_static_cache_capture_ensure_table(HashTable **table_ptr) { if (*table_ptr == NULL) { @@ -1113,6 +1119,7 @@ static bool zend_opcache_static_cache_detach_class_blob_root_state( ? zend_hash_num_elements(Z_ARRVAL_P(methods)) : 0 ); + if (methods != NULL && Z_TYPE_P(methods) == IS_ARRAY) { ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(methods), method_key, method_state) { if (method_key == NULL) { @@ -1763,7 +1770,12 @@ static void zend_opcache_static_cache_track_value_recursive( continue; } - source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + source_value = + Z_TYPE_P(property_value) == IS_INDIRECT + ? Z_INDIRECT_P(property_value) + : property_value + ; + zend_opcache_static_cache_track_value_recursive(source_value, handle, seen_arrays, seen_objects); } ZEND_HASH_FOREACH_END(); @@ -1784,7 +1796,8 @@ static bool zend_opcache_static_cache_prepare_memo_can_track_object(zend_class_e ce->__serialize == NULL && ce->__unserialize == NULL && !zend_hash_str_exists(&ce->function_table, ZEND_STRL("__sleep")) && - !zend_hash_str_exists(&ce->function_table, ZEND_STRL("__wakeup")); + !zend_hash_str_exists(&ce->function_table, ZEND_STRL("__wakeup")) + ; } static bool zend_opcache_static_cache_prepare_memo_can_track_value_recursive( @@ -1820,6 +1833,7 @@ static bool zend_opcache_static_cache_prepare_memo_can_track_value_recursive( } zend_hash_index_add_empty_element(seen_arrays, key); + ZEND_HASH_FOREACH_VAL(ht, child) { if (!zend_opcache_static_cache_prepare_memo_can_track_value_recursive(child, seen_arrays, seen_objects)) { return false; @@ -1849,7 +1863,12 @@ static bool zend_opcache_static_cache_prepare_memo_can_track_value_recursive( continue; } - source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + source_value = + Z_TYPE_P(property_value) == IS_INDIRECT + ? Z_INDIRECT_P(property_value) + : property_value + ; + if (!zend_opcache_static_cache_prepare_memo_can_track_value_recursive(source_value, seen_arrays, seen_objects)) { zend_release_properties(properties); return false; @@ -1892,6 +1911,7 @@ static void zend_opcache_static_cache_prepare_memo_track_value_recursive( zend_hash_index_add_empty_element(seen_arrays, key); zend_opcache_static_cache_prepare_memo_track_dependency(zend_opcache_static_cache_prepare_memo_arrays, key, entry); zend_opcache_static_cache_has_prepare_memo_arrays = true; + ZEND_HASH_FOREACH_VAL(ht, child) { zend_opcache_static_cache_prepare_memo_track_value_recursive(child, entry, seen_arrays, seen_objects); } ZEND_HASH_FOREACH_END(); @@ -1917,7 +1937,12 @@ static void zend_opcache_static_cache_prepare_memo_track_value_recursive( continue; } - source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + source_value = + Z_TYPE_P(property_value) == IS_INDIRECT + ? Z_INDIRECT_P(property_value) + : property_value + ; + zend_opcache_static_cache_prepare_memo_track_value_recursive(source_value, entry, seen_arrays, seen_objects); } ZEND_HASH_FOREACH_END(); @@ -2666,8 +2691,9 @@ void zend_opcache_static_cache_delete_class_keys_locked(zend_class_entry *ce) } kind = zend_opcache_static_cache_static_property_kind(ce, property_info); - if (kind == ZEND_OPCACHE_STATIC_CACHE_NONE - || zend_opcache_static_cache_context_for_kind(kind) != zend_opcache_static_cache_active_context()) { + if (kind == ZEND_OPCACHE_STATIC_CACHE_NONE || + zend_opcache_static_cache_context_for_kind(kind) != zend_opcache_static_cache_active_context() + ) { continue; } @@ -2788,6 +2814,7 @@ static void zend_opcache_static_cache_publish_pinned_static_properties_fast(zend published = false; previous_context = zend_opcache_static_cache_activate_context(context); + if (zend_opcache_static_cache_wlock()) { if (zend_opcache_static_cache_header_init_locked()) { /* Immediate property assignments publish immediately so later @@ -2970,12 +2997,6 @@ static void zend_opcache_static_cache_publish_context(zend_opcache_static_cache_ zend_opcache_static_cache_restore_context(previous_context); } -static void zend_opcache_static_cache_capture_decoded_reachable_value_ex( - zval *value, - HashTable *seen_arrays, - HashTable *seen_objects -); - static void zend_opcache_static_cache_capture_decoded_hash_values( HashTable *values, HashTable *seen_arrays, @@ -3026,6 +3047,7 @@ static bool zend_opcache_static_cache_capture_decoded_safe_direct_object( if (serialize_func(value, &state) && Z_TYPE(state) == IS_ARRAY) { zend_opcache_static_cache_capture_decoded_hash_values(Z_ARRVAL(state), seen_arrays, seen_objects); } + if (!Z_ISUNDEF(state)) { zval_ptr_dtor(&state); } @@ -3111,7 +3133,13 @@ static void zend_opcache_static_cache_capture_decoded_reachable_value_ex( if (property_name == NULL) { continue; } - source_value = Z_TYPE_P(property_value) == IS_INDIRECT ? Z_INDIRECT_P(property_value) : property_value; + + source_value = + Z_TYPE_P(property_value) == IS_INDIRECT + ? Z_INDIRECT_P(property_value) + : property_value + ; + zend_opcache_static_cache_capture_decoded_reachable_value_ex(source_value, seen_arrays, seen_objects); } ZEND_HASH_FOREACH_END(); @@ -3137,8 +3165,7 @@ static bool zend_opcache_static_cache_hash_mutation(HashTable *ht, bool publish) zend_opcache_static_cache_tracks_reachable_arrays( zend_opcache_static_cache_pending_mutation_state.handle) && !zend_opcache_static_cache_pending_hash_is_current_reachable_array( - zend_opcache_static_cache_pending_mutation_state.handle, - ht) + zend_opcache_static_cache_pending_mutation_state.handle, ht) ) { zend_opcache_static_cache_reset_pending_mutation(); } else { @@ -3308,6 +3335,7 @@ static void zend_opcache_static_cache_init_class(zend_class_entry *ce) handle = zend_opcache_static_cache_ensure_tracked_root(slot, cache_key, context, kind, ttl, NULL); zend_opcache_static_cache_capture_begin_for_handle(handle); + if (zend_opcache_static_cache_fetch_locked(cache_key, &cached_value, false, NULL, false)) { fetched_cached = true; zval_ptr_dtor(slot); @@ -3527,6 +3555,7 @@ static void zend_opcache_static_cache_init_function(zend_execute_data *execute_d handle = zend_opcache_static_cache_ensure_tracked_root(slot, cache_key, context, kind, ttl, NULL); zend_opcache_static_cache_capture_begin_for_handle(handle); + if (zend_opcache_static_cache_fetch_locked(cache_key, &cached_value, false, NULL, false)) { fetched_cached = true; zval_ptr_dtor(slot); @@ -3657,7 +3686,8 @@ void zend_opcache_static_cache_prepare_memo_store( zend_hash_index_update_ptr( zend_opcache_static_cache_prepare_memo_entries, (zend_ulong) (uintptr_t) entry, - entry); + entry + ); roots = zend_opcache_static_cache_prepare_memo_root_table_for_value(value); key = zend_opcache_static_cache_prepare_memo_root_key(value); @@ -3818,7 +3848,10 @@ void zend_opcache_static_cache_capture_decoded_value(zval *value) void zend_opcache_static_cache_request_shutdown(void) { - if (zend_opcache_static_cache_attribute_classes_initialized || zend_opcache_static_cache_function_statics_initialized || zend_opcache_static_cache_class_blob_handles_initialized) { + if (zend_opcache_static_cache_attribute_classes_initialized || + zend_opcache_static_cache_function_statics_initialized || + zend_opcache_static_cache_class_blob_handles_initialized + ) { zend_opcache_static_cache_flush_pending(); zend_opcache_static_cache_publish_context(&zend_opcache_static_cache_pinned_context_state); zend_opcache_static_cache_publish_context(&zend_opcache_static_cache_volatile_context_state); diff --git a/ext/opcache/zend_static_cache_storage.c b/ext/opcache/zend_static_cache_storage.c index 70179275ca4d..537d2045f051 100644 --- a/ext/opcache/zend_static_cache_storage.c +++ b/ext/opcache/zend_static_cache_storage.c @@ -55,7 +55,19 @@ static zend_always_inline bool zend_opcache_static_cache_force_startup_failure(v static zend_always_inline bool zend_opcache_static_cache_requires_pre_request_storage(void) { - return sapi_module.name != NULL && strcmp(sapi_module.name, "fpm-fcgi") == 0; + if (sapi_module.name == NULL) { + return false; + } + + /* + * These SAPIs may fork or spawn long-lived workers after module startup. + * Request-time initialization would allow a worker-local backend instead + * of the shared static cache storage expected by all workers. + */ + return strcmp(sapi_module.name, "fpm-fcgi") == 0 || + strcmp(sapi_module.name, "apache2handler") == 0 || + strcmp(sapi_module.name, "litespeed") == 0 || + strcmp(sapi_module.name, "cgi-fcgi") == 0; } static zend_always_inline void zend_opcache_static_cache_set_unavailable(const char *failure_reason, bool startup_failed) @@ -1241,12 +1253,14 @@ static void zend_opcache_static_cache_ensure_entry_lock_process(void) &zend_opcache_static_cache_volatile_context_state, &zend_opcache_static_cache_volatile_entry_locks, zend_opcache_static_cache_volatile_entry_lock_counts, - ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP); + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP + ); zend_opcache_static_cache_release_entry_lock_context( &zend_opcache_static_cache_pinned_context_state, &zend_opcache_static_cache_pinned_entry_locks, zend_opcache_static_cache_pinned_entry_lock_counts, - ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP); + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_DROP + ); #ifdef ZTS zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_volatile_context_state.storage); zend_opcache_static_cache_entry_locks_reinit_after_fork(&zend_opcache_static_cache_pinned_context_state.storage); @@ -1944,6 +1958,7 @@ void zend_opcache_static_cache_reset_runtime(void) ? ZCG(accel_directives).static_cache_pinned_size_mb : ZCG(accel_directives).static_cache_volatile_size_mb ; + runtime->enabled = runtime->configured_memory != 0; if (zend_opcache_static_cache_subsystem_disabled) { @@ -2226,7 +2241,7 @@ void zend_opcache_static_cache_ensure_ready(void) if (!storage->initialized && zend_opcache_static_cache_requires_pre_request_storage() ) { - zend_opcache_static_cache_set_unavailable("Shared memory backend was not initialized before FPM worker startup", true); + zend_opcache_static_cache_set_unavailable("Shared memory backend was not initialized before worker startup", true); return; } @@ -2240,7 +2255,7 @@ void zend_opcache_static_cache_ensure_ready(void) if (zend_opcache_static_cache_requires_pre_request_storage() && !storage->initialized_before_request ) { - zend_opcache_static_cache_set_unavailable("Shared memory backend was initialized after FPM worker startup", true); + zend_opcache_static_cache_set_unavailable("Shared memory backend was initialized after worker startup", true); return; } @@ -2461,7 +2476,8 @@ void zend_opcache_static_cache_release_active_entry_locks(void) context, zend_opcache_static_cache_entry_locks_ptr_for_context(context), zend_opcache_static_cache_entry_lock_counts_for_context(context), - ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_CLEAR); + ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_CLEAR + ); } void zend_opcache_static_cache_release_request_entry_locks(void) @@ -2472,6 +2488,7 @@ void zend_opcache_static_cache_release_request_entry_locks(void) zend_opcache_static_cache_volatile_entry_lock_counts, ZEND_OPCACHE_STATIC_CACHE_ENTRY_LOCK_RELEASE_PRESERVE_LEASES ); + zend_opcache_static_cache_release_entry_lock_context( &zend_opcache_static_cache_pinned_context_state, &zend_opcache_static_cache_pinned_entry_locks, diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index 936d0e853654..8ceb4d2498af 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -680,8 +680,7 @@ static bool spl_fixedarray_object_copy_direct_cache_state( zend_object *new_object, zend_opcache_static_cache_safe_direct_clone_value_func_t clone_value) { - zval old_zv, new_zv, state_zv, cloned_state_zv, elements_zv, copied_elem; - zval *elem; + zval old_zv, new_zv, state_zv, cloned_state_zv, elements_zv, copied_elem, *elem; zend_string *key; bool result = false; @@ -722,6 +721,7 @@ static bool spl_fixedarray_object_copy_direct_cache_state( if (Z_TYPE(cloned_state_zv) != IS_UNDEF) { zval_ptr_dtor(&cloned_state_zv); } + if (Z_TYPE(state_zv) != IS_UNDEF) { zval_ptr_dtor(&state_zv); } @@ -743,6 +743,7 @@ static bool spl_fixedarray_object_direct_cache_state_has_unstorable( ZVAL_UNDEF(&state_zv); spl_fixedarray_object_serialize((zval *) object, &state_zv); + if (EG(exception) || Z_TYPE(state_zv) == IS_UNDEF) { return true; } @@ -762,6 +763,7 @@ static bool spl_fixedarray_object_serialize_direct_cache_state(const zval *objec ZVAL_UNDEF(state); ZVAL_UNDEF(&serialized_state); spl_fixedarray_object_serialize((zval *) object, &serialized_state); + if (EG(exception) || Z_TYPE(serialized_state) != IS_ARRAY) { if (Z_TYPE(serialized_state) != IS_UNDEF) { zval_ptr_dtor(&serialized_state);