Make WordPress Core


Ignore:
Timestamp:
11/07/2025 06:11:24 AM (9 months ago)
Author:
westonruter
Message:

Script Loader: Guard against exponential recursion during calculation of loading strategy and fetchpriority.

This addresses a performance issue in the recursive WP_Scripts::get_highest_fetchpriority_with_dependents() and WP_Scripts::filter_eligible_strategies() methods for redundant processing of shared dependencies in complex dependency graphs. To fix this, a $stored_results param is introduced which is passed by reference; this variable contains a cache of the calculated results for all scripts handles, so that subsequent calls for the same handle can return the cached value instead of re-computing it.

Developed in https://github.com/WordPress/wordpress-develop/pull/10459

Follow-up to [60704], [60931], [56033].

Props ciobanucatalin, b1ink0, westonruter, mukesh27.
See #61734, #12009.
Fixes #64194.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-scripts.php

    r60948 r61176  
    998998         * @since 6.3.0
    999999         *
    1000          * @param string              $handle              The script handle.
    1001          * @param string[]|null       $eligible_strategies Optional. The list of strategies to filter. Default null.
    1002          * @param array<string, true> $checked             Optional. An array of already checked script handles, used to avoid recursive loops.
     1000         * @param string                  $handle              The script handle.
     1001         * @param string[]|null           $eligible_strategies Optional. The list of strategies to filter. Default null.
     1002         * @param array<string, true>     $checked             Optional. An array of already checked script handles, used to avoid recursive loops.
     1003         * @param array<string, string[]> $stored_results      Optional. An array of already computed eligible loading strategies by handle, used to increase performance in large dependency lists.
    10031004         * @return string[] A list of eligible loading strategies that could be used.
    10041005         */
    1005         private function filter_eligible_strategies( $handle, $eligible_strategies = null, $checked = array() ) {
     1006        private function filter_eligible_strategies( $handle, $eligible_strategies = null, $checked = array(), array &$stored_results = array() ) {
     1007                if ( isset( $stored_results[ $handle ] ) ) {
     1008                        return $stored_results[ $handle ];
     1009                }
     1010
    10061011                // If no strategies are being passed, all strategies are eligible.
    10071012                if ( null === $eligible_strategies ) {
     
    10541059                        }
    10551060
    1056                         $eligible_strategies = $this->filter_eligible_strategies( $dependent, $eligible_strategies, $checked );
    1057                 }
    1058 
     1061                        $eligible_strategies = $this->filter_eligible_strategies( $dependent, $eligible_strategies, $checked, $stored_results );
     1062                }
     1063                $stored_results[ $handle ] = $eligible_strategies;
    10591064                return $eligible_strategies;
    10601065        }
     
    10671072         * @see WP_Script_Modules::get_highest_fetchpriority_with_dependents()
    10681073         *
    1069          * @param string              $handle  Script module ID.
    1070          * @param array<string, true> $checked Optional. An array of already checked script handles, used to avoid recursive loops.
     1074         * @param string                $handle         Script module ID.
     1075         * @param array<string, true>   $checked        Optional. An array of already checked script handles, used to avoid recursive loops.
     1076         * @param array<string, string> $stored_results Optional. An array of already computed max priority by handle, used to increase performance in large dependency lists.
    10711077         * @return string|null Highest fetch priority for the script and its dependents.
    10721078         */
    1073         private function get_highest_fetchpriority_with_dependents( string $handle, array $checked = array() ): ?string {
     1079        private function get_highest_fetchpriority_with_dependents( string $handle, array $checked = array(), array &$stored_results = array() ): ?string {
     1080                if ( isset( $stored_results[ $handle ] ) ) {
     1081                        return $stored_results[ $handle ];
     1082                }
     1083
    10741084                // If there is a recursive dependency, return early.
    10751085                if ( isset( $checked[ $handle ] ) ) {
     
    11001110                if ( $highest_priority_index !== $high_priority_index ) {
    11011111                        foreach ( $this->get_dependents( $handle ) as $dependent_handle ) {
    1102                                 $dependent_priority = $this->get_highest_fetchpriority_with_dependents( $dependent_handle, $checked );
     1112                                $dependent_priority = $this->get_highest_fetchpriority_with_dependents( $dependent_handle, $checked, $stored_results );
    11031113                                if ( is_string( $dependent_priority ) ) {
    11041114                                        $highest_priority_index = max(
     
    11121122                        }
    11131123                }
    1114 
     1124                $stored_results[ $handle ] = $priorities[ $highest_priority_index ]; // @phpstan-ignore parameterByRef.type (We know the index is valid and that this will be a string.)
    11151125                return $priorities[ $highest_priority_index ];
    11161126        }
Note: See TracChangeset for help on using the changeset viewer.