@@ -83,6 +83,11 @@ typedef struct _sccp_ctx {
83
83
zval bot ;
84
84
} sccp_ctx ;
85
85
86
+ typedef struct _named_arg_pair {
87
+ zval * name ;
88
+ zval * value ;
89
+ } named_arg_pair ;
90
+
86
91
#define TOP ((uint8_t)-1)
87
92
#define BOT ((uint8_t)-2)
88
93
#define PARTIAL_ARRAY ((uint8_t)-3)
@@ -744,7 +749,7 @@ static inline zend_result ct_eval_array_key_exists(zval *result, zval *op1, zval
744
749
return SUCCESS ;
745
750
}
746
751
747
- static bool can_ct_eval_func_call (zend_function * func , zend_string * name , uint32_t num_args , zval * * args ) {
752
+ static bool can_ct_eval_func_call (zend_function * func , zend_string * name , uint32_t num_args , zval * * args , uint32_t num_named_args ) {
748
753
/* Precondition: func->type == ZEND_INTERNAL_FUNCTION, this is a global function */
749
754
/* Functions setting ZEND_ACC_COMPILE_TIME_EVAL (@compile-time-eval) must always produce the same result for the same arguments,
750
755
* and have no dependence on global state (such as locales). It is okay if they throw
@@ -753,6 +758,13 @@ static bool can_ct_eval_func_call(zend_function *func, zend_string *name, uint32
753
758
/* This has @compile-time-eval in stub info and uses a macro such as ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE */
754
759
return true;
755
760
}
761
+
762
+ /* Has a named argument, but dirname doesn't expect that, and checking the str_repeat case is too complex.
763
+ * The complexity is not worth it for one function which will unlikely be used with named parameters. */
764
+ if (num_named_args > 0 ) {
765
+ return false;
766
+ }
767
+
756
768
#ifndef ZEND_WIN32
757
769
/* On Windows this function may be code page dependent. */
758
770
if (zend_string_equals_literal (name , "dirname" )) {
@@ -779,7 +791,7 @@ static bool can_ct_eval_func_call(zend_function *func, zend_string *name, uint32
779
791
* or just happened to be commonly used with constant operands in WP (need to test other
780
792
* applications as well, of course). */
781
793
static inline zend_result ct_eval_func_call (
782
- zend_op_array * op_array , zval * result , zend_string * name , uint32_t num_args , zval * * args ) {
794
+ zend_op_array * op_array , zval * result , zend_string * name , uint32_t num_args , zval * * args , named_arg_pair * named_args , uint32_t num_named_args ) {
783
795
uint32_t i ;
784
796
zend_function * func = zend_hash_find_ptr (CG (function_table ), name );
785
797
if (!func || func -> type != ZEND_INTERNAL_FUNCTION ) {
@@ -791,7 +803,7 @@ static inline zend_result ct_eval_func_call(
791
803
return SUCCESS ;
792
804
}
793
805
794
- if (!can_ct_eval_func_call (func , name , num_args , args )) {
806
+ if (!can_ct_eval_func_call (func , name , num_args , args , num_named_args )) {
795
807
return FAILURE ;
796
808
}
797
809
@@ -821,12 +833,47 @@ static inline zend_result ct_eval_func_call(
821
833
ZVAL_COPY (EX_VAR_NUM (i ), args [i ]);
822
834
}
823
835
ZVAL_NULL (result );
824
- func -> internal_function .handler (execute_data , result );
836
+
837
+ zend_result retval = SUCCESS ;
838
+ zval * named_args_copies [3 ] = {NULL };
839
+ ZEND_ASSERT (num_named_args <= sizeof (named_args_copies ) / sizeof (named_args_copies [0 ]));
840
+
841
+ for (i = 0 ; i < num_named_args ; i ++ ) {
842
+ uint32_t arg_num_unused ;
843
+ /* Need 2 cache slots for zend_get_arg_offset_by_name() */
844
+ void * cache_slots [2 ] = {NULL };
845
+ zval * arg = zend_handle_named_arg (& execute_data , Z_STR_P (named_args [i ].name ), & arg_num_unused , cache_slots );
846
+ if (!arg ) {
847
+ retval = FAILURE ;
848
+ break ;
849
+ }
850
+ ZVAL_COPY (arg , named_args [i ].value );
851
+ named_args_copies [i ] = arg ;
852
+ }
853
+
854
+ if (retval == SUCCESS ) {
855
+ /* Handle undef arguments in the same way as how the VM does it */
856
+ if (UNEXPECTED (ZEND_CALL_INFO (execute_data ) & ZEND_CALL_MAY_HAVE_UNDEF )) {
857
+ /* Have to hackisly set the current EX() back one frame because zend_handle_undef_args()
858
+ * temporarily starts its own "fake frame" for execute_data. */
859
+ EG (current_execute_data ) = & dummy_frame ;
860
+ retval = zend_handle_undef_args (execute_data );
861
+ EG (current_execute_data ) = execute_data ;
862
+ }
863
+ if (retval == SUCCESS ) {
864
+ func -> internal_function .handler (execute_data , result );
865
+ }
866
+ }
867
+
825
868
for (i = 0 ; i < num_args ; i ++ ) {
826
869
zval_ptr_dtor_nogc (EX_VAR_NUM (i ));
827
870
}
871
+ for (i = 0 ; i < num_named_args ; i ++ ) {
872
+ if (named_args_copies [i ]) {
873
+ zval_ptr_dtor_nogc (named_args_copies [i ]);
874
+ }
875
+ }
828
876
829
- zend_result retval = SUCCESS ;
830
877
if (EG (exception )) {
831
878
zval_ptr_dtor (result );
832
879
zend_clear_exception ();
@@ -1631,7 +1678,8 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
1631
1678
{
1632
1679
zend_call_info * call ;
1633
1680
zval * name , * args [3 ] = {NULL };
1634
- int i ;
1681
+ named_arg_pair named_args [3 ] = {{NULL , NULL }};
1682
+ unsigned int i ;
1635
1683
1636
1684
if (!ctx -> call_map ) {
1637
1685
SET_RESULT_BOT (result );
@@ -1646,9 +1694,8 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
1646
1694
break ;
1647
1695
}
1648
1696
1649
- /* We're only interested in functions with up to three arguments right now.
1650
- * Note that named arguments with the argument in declaration order will still work. */
1651
- if (call -> num_args > 3 || call -> send_unpack || call -> is_prototype || call -> named_args ) {
1697
+ /* We're only interested in functions with up to three positional arguments right now. */
1698
+ if (call -> num_args > 3 || call -> send_unpack || call -> is_prototype ) {
1652
1699
SET_RESULT_BOT (result );
1653
1700
break ;
1654
1701
}
@@ -1672,12 +1719,44 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
1672
1719
}
1673
1720
}
1674
1721
1722
+ i = 0 ;
1723
+ if (call -> first_named_arg .opline ) {
1724
+ for (zend_op * opline = call -> first_named_arg .opline ; opline != call -> caller_call_opline ; opline ++ , i ++ ) {
1725
+ if (opline -> opcode == ZEND_CHECK_UNDEF_ARGS ) {
1726
+ break ;
1727
+ }
1728
+ if ((opline -> opcode != ZEND_SEND_VAL && opline -> opcode != ZEND_SEND_VAR )
1729
+ /* must have a name, which is a const */
1730
+ || opline -> op2_type != IS_CONST
1731
+ /* must not exceed the maximum number of named parameters */
1732
+ || i == sizeof (named_args ) / sizeof (named_args [0 ])) {
1733
+ SET_RESULT_BOT (result );
1734
+ return ;
1735
+ }
1736
+ zval * argument_name = get_op2_value (ctx , opline ,
1737
+ & ctx -> scdf .ssa -> ops [opline - ctx -> scdf .op_array -> opcodes ]);
1738
+ ZEND_ASSERT (Z_TYPE_P (argument_name ) == IS_STRING );
1739
+ zval * argument_value = get_op1_value (ctx , opline ,
1740
+ & ctx -> scdf .ssa -> ops [opline - ctx -> scdf .op_array -> opcodes ]);
1741
+ if (argument_value ) {
1742
+ if (IS_BOT (argument_value ) || IS_PARTIAL_ARRAY (argument_value )) {
1743
+ SET_RESULT_BOT (result );
1744
+ return ;
1745
+ } else if (IS_TOP (argument_value )) {
1746
+ return ;
1747
+ }
1748
+ named_args [i ].name = argument_name ;
1749
+ named_args [i ].value = argument_value ;
1750
+ }
1751
+ }
1752
+ }
1753
+
1675
1754
/* We didn't get a BOT argument, so value stays the same */
1676
1755
if (!IS_TOP (& ctx -> values [ssa_op -> result_def ])) {
1677
1756
break ;
1678
1757
}
1679
1758
1680
- if (ct_eval_func_call (scdf -> op_array , & zv , Z_STR_P (name ), call -> num_args , args ) == SUCCESS ) {
1759
+ if (ct_eval_func_call (scdf -> op_array , & zv , Z_STR_P (name ), call -> num_args , args , named_args , i ) == SUCCESS ) {
1681
1760
SET_RESULT (result , & zv );
1682
1761
zval_ptr_dtor_nogc (& zv );
1683
1762
break ;
@@ -2023,7 +2102,6 @@ static int remove_call(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op)
2023
2102
zend_ssa * ssa = ctx -> scdf .ssa ;
2024
2103
zend_op_array * op_array = ctx -> scdf .op_array ;
2025
2104
zend_call_info * call ;
2026
- int i ;
2027
2105
2028
2106
ZEND_ASSERT (ctx -> call_map );
2029
2107
call = ctx -> call_map [opline - op_array -> opcodes ];
@@ -2033,15 +2111,23 @@ static int remove_call(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op)
2033
2111
zend_ssa_remove_instr (ssa , call -> caller_init_opline ,
2034
2112
& ssa -> ops [call -> caller_init_opline - op_array -> opcodes ]);
2035
2113
2036
- for (i = 0 ; i < call -> num_args ; i ++ ) {
2114
+ int removed = 2 + call -> num_args ;
2115
+ for (int i = 0 ; i < call -> num_args ; i ++ ) {
2037
2116
zend_ssa_remove_instr (ssa , call -> arg_info [i ].opline ,
2038
2117
& ssa -> ops [call -> arg_info [i ].opline - op_array -> opcodes ]);
2039
2118
}
2119
+ zend_op * named_arg = call -> first_named_arg .opline ;
2120
+ if (named_arg ) {
2121
+ for (; named_arg != opline ; named_arg ++ , removed ++ ) {
2122
+ zend_ssa_remove_instr (ssa , named_arg ,
2123
+ & ssa -> ops [named_arg - op_array -> opcodes ]);
2124
+ }
2125
+ }
2040
2126
2041
2127
// TODO: remove call_info completely???
2042
2128
call -> callee_func = NULL ;
2043
2129
2044
- return call -> num_args + 2 ;
2130
+ return removed ;
2045
2131
}
2046
2132
2047
2133
/* This is a basic DCE pass we run after SCCP. It only works on those instructions those result
0 commit comments