@@ -30,7 +30,7 @@ zend_string *pt_csi_return_template_name = nullptr;
3030zend_string *pt_csi_closed_body_attribute = nullptr ;
3131zend_string *pt_csi_returns_context_typed_attribute = nullptr ;
3232zend_string *pt_csi_assigned_closures_attribute = nullptr ;
33- zend_string *pt_csi_nested_assigned_closures_attribute = nullptr ;
33+ zend_string *pt_csi_assigned_to_by_ref_use_attribute = nullptr ;
3434/* the by-ref follow-up's template names and attribute names */
3535zend_string *pt_csi_invocation_template_name = nullptr ;
3636zend_string *pt_csi_by_ref_uses_attribute = nullptr ;
@@ -231,10 +231,9 @@ void containsMarkerBody(zval *contains, zval *state1, uint32_t argc, zval *argv,
231231}
232232
233233/* Mirrors the private static getAssignedClosures(); UNDEF = pending exception */
234- zv::Val getAssignedClosures (zval *body, zval *stmts, bool nested = false )
234+ zv::Val getAssignedClosures (zval *body, zval *stmts)
235235{
236- zend_string *attribute = nested ? pt_csi_nested_assigned_closures_attribute : pt_csi_assigned_closures_attribute;
237- zv::Val cached = pt_engine_node_get_attribute (Z_OBJ_P (body), ZSTR_VAL (attribute), ZSTR_LEN (attribute));
236+ zv::Val cached = pt_engine_node_get_attribute (Z_OBJ_P (body), ZSTR_VAL (pt_csi_assigned_closures_attribute), ZSTR_LEN (pt_csi_assigned_closures_attribute));
238237 if (UNEXPECTED (cached.isUndef ())) return zv::Val ();
239238 if (!cached.isNull ()) return cached;
240239
@@ -248,17 +247,15 @@ zv::Val getAssignedClosures(zval *body, zval *stmts, bool nested = false)
248247 for (;;) {
249248 zv::Val node = popNode (stack);
250249 if (node.isUndef ()) break ;
251- if (!nested) {
252- bool skip;
253- if (UNEXPECTED (!isA (node.raw (), PT_CLASS_FUNCTION_LIKE , skip))) return zv::Val ();
254- if (!skip && UNEXPECTED (!isA (node.raw (), PT_CLASS_CLASS_LIKE_STMT , skip))) return zv::Val ();
255- if (skip) continue ;
256- }
250+ bool skip;
251+ if (UNEXPECTED (!isA (node.raw (), PT_CLASS_FUNCTION_LIKE , skip))) return zv::Val ();
252+ if (!skip && UNEXPECTED (!isA (node.raw (), PT_CLASS_CLASS_LIKE_STMT , skip))) return zv::Val ();
253+ if (skip) continue ;
257254 if (UNEXPECTED (!collectAssignedClosure (node.raw (), assigned))) return zv::Val ();
258255 if (UNEXPECTED (!pushSubNodes (node.raw (), stack))) return zv::Val ();
259256 }
260257 zv::Val result (std::move (assigned));
261- if (UNEXPECTED (!pt_engine_node_set_attribute (Z_OBJ_P (body), ZSTR_VAL (attribute ), ZSTR_LEN (attribute ), result.raw ()))) return zv::Val ();
258+ if (UNEXPECTED (!pt_engine_node_set_attribute (Z_OBJ_P (body), ZSTR_VAL (pt_csi_assigned_closures_attribute ), ZSTR_LEN (pt_csi_assigned_closures_attribute ), result.raw ()))) return zv::Val ();
262259 return result;
263260}
264261
@@ -577,6 +574,81 @@ zv::Val anonymousMarker(zval *site, zend_string *name, zend_long variance, zval
577574 return true ;
578575}
579576
577+ /* Mirrors the private static isAssignedToByRefUse(); false = pending exception */
578+ [[nodiscard]] bool isAssignedToByRefUse (zval *expr, zval *stmts, bool &out)
579+ {
580+ const char *attribute = ZSTR_VAL (pt_csi_assigned_to_by_ref_use_attribute);
581+ size_t attributeLength = ZSTR_LEN (pt_csi_assigned_to_by_ref_use_attribute);
582+ zv::Val cached = pt_engine_node_get_attribute (Z_OBJ_P (expr), attribute, attributeLength);
583+ if (UNEXPECTED (cached.isUndef ())) return false ;
584+ if (Z_TYPE_P (cached.raw ()) == IS_TRUE || Z_TYPE_P (cached.raw ()) == IS_FALSE ) {
585+ out = Z_TYPE_P (cached.raw ()) == IS_TRUE ;
586+ return true ;
587+ }
588+
589+ zval marked;
590+ zv::Arr stack = zv::Arr::create (0 );
591+ if (Z_TYPE_P (stmts) == IS_ARRAY ) {
592+ for (auto entry : zv::ArrRef (stmts)) {
593+ stack.push (zv::Val::copyOf (entry.value ().deref ()));
594+ }
595+ }
596+ for (;;) {
597+ zv::Val node = popNode (stack);
598+ if (node.isUndef ()) break ;
599+ zval *n = node.raw ();
600+ bool is;
601+ if (UNEXPECTED (!isA (n, PT_CLASS_ASSIGN_EXPR , is))) return false ;
602+ if (is) {
603+ zval *var = read (pt_csi_assign_var_site, n, PT_LC (" var" ));
604+ if (UNEXPECTED (var == NULL )) return false ;
605+ zend_string *name;
606+ if (UNEXPECTED (!variableStringName (pt_csi_variable_name_site, var, name))) return false ;
607+ zval *value = read (pt_csi_assign_expr_site, n, PT_LC (" expr" ));
608+ if (UNEXPECTED (value == NULL )) return false ;
609+ bool isClosure;
610+ if (UNEXPECTED (!isA (value, PT_CLASS_CLOSURE_EXPR , isClosure))) return false ;
611+ if (name != NULL && isClosure) {
612+ zval *uses = read (pt_csi_by_ref_uses_site, value, PT_LC (" uses" ));
613+ if (UNEXPECTED (uses == NULL )) return false ;
614+ zv::Val usesHold = zv::Val::copyOf (zv::Ref (uses));
615+ for (auto entry : zv::ArrRef (usesHold.raw ())) {
616+ zval *use = entry.value ().deref ().raw ();
617+ zval *byRef = read (pt_csi_by_ref_use_by_ref_site, use, PT_LC (" byRef" ));
618+ if (UNEXPECTED (byRef == NULL )) return false ;
619+ if (!zend_is_true (byRef)) continue ;
620+ zval *useVar = read (pt_csi_by_ref_use_var_site, use, PT_LC (" var" ));
621+ if (UNEXPECTED (useVar == NULL )) return false ;
622+ zval *useName = read (pt_csi_by_ref_variable_name_site, useVar, PT_LC (" name" ));
623+ if (UNEXPECTED (useName == NULL )) return false ;
624+ if (Z_TYPE_P (useName) != IS_STRING || !zend_string_equals (Z_STR_P (useName), name)) continue ;
625+ ZVAL_TRUE (&marked);
626+ if (UNEXPECTED (!pt_engine_node_set_attribute (Z_OBJ_P (value), attribute, attributeLength, &marked))) return false ;
627+ break ;
628+ }
629+ }
630+ } else {
631+ if (UNEXPECTED (!isA (n, PT_CLASS_CLOSURE_EXPR , is))) return false ;
632+ if (is) {
633+ zv::Val decided = pt_engine_node_get_attribute (Z_OBJ_P (n), attribute, attributeLength);
634+ if (UNEXPECTED (decided.isUndef ())) return false ;
635+ if (decided.isNull ()) {
636+ // an assignment is visited before the closure it assigns
637+ ZVAL_FALSE (&marked);
638+ if (UNEXPECTED (!pt_engine_node_set_attribute (Z_OBJ_P (n), attribute, attributeLength, &marked))) return false ;
639+ }
640+ }
641+ }
642+ if (UNEXPECTED (!pushSubNodes (n, stack))) return false ;
643+ }
644+
645+ zv::Val decided = pt_engine_node_get_attribute (Z_OBJ_P (expr), attribute, attributeLength);
646+ if (UNEXPECTED (decided.isUndef ())) return false ;
647+ out = Z_TYPE_P (decided.raw ()) == IS_TRUE ;
648+ ZVAL_BOOL (&marked, out);
649+ return pt_engine_node_set_attribute (Z_OBJ_P (expr), attribute, attributeLength, &marked);
650+ }
651+
580652/* Mirrors the private bodyYieldsOrInvokes(); false = pending exception */
581653[[nodiscard]] bool bodyYieldsOrInvokes (zval *stmts, zval *names, bool &out, bool checkYield = true )
582654{
@@ -1077,8 +1149,12 @@ class ClosureSignatureInference
10771149 if (UNEXPECTED (stmts.isUndef ())) return zv::Val ();
10781150 zv::Val reboundNames = getReboundVariableNames (body.raw (), stmts.raw ());
10791151 if (UNEXPECTED (reboundNames.isUndef ())) return zv::Val ();
1080- zv::Val assignedClosures = getAssignedClosures (body.raw (), stmts.raw (), true );
1081- if (UNEXPECTED (assignedClosures.isUndef ())) return zv::Val ();
1152+ bool assignedToByRefUse = false ;
1153+ if (UNEXPECTED (!isAssignedToByRefUse (expr, stmts.raw (), assignedToByRefUse))) return zv::Val ();
1154+ // `$f = function () use (&$f)`: the body reaches the closure itself
1155+ // through the variable - aliased, captured, passed on - so its
1156+ // invocations cannot all be followed
1157+ if (assignedToByRefUse) return zv::Val (zv::Arr::empty ());
10821158 for (auto entry : zv::ArrRef (names.raw ())) {
10831159 zval *name = entry.value ().deref ().raw ();
10841160 if (Z_TYPE_P (name) != IS_STRING ) continue ;
@@ -1087,18 +1163,6 @@ class ClosureSignatureInference
10871163 if (zend_symtable_find (Z_ARRVAL_P (reboundNames.raw ()), Z_STR_P (name)) != NULL ) {
10881164 return zv::Val (zv::Arr::empty ());
10891165 }
1090- // `$f = function () use (&$f)`: the body reaches the closure itself
1091- // through the variable - aliased, captured, passed on - so its
1092- // invocations cannot all be followed
1093- zval *closures = zend_symtable_find (Z_ARRVAL_P (assignedClosures.raw ()), Z_STR_P (name));
1094- if (closures != NULL && Z_TYPE_P (closures) == IS_ARRAY ) {
1095- for (auto closure : zv::ArrRef (closures)) {
1096- zval *candidate = closure.value ().deref ().raw ();
1097- if (Z_TYPE_P (candidate) == IS_OBJECT && Z_OBJ_P (candidate) == Z_OBJ_P (expr)) {
1098- return zv::Val (zv::Arr::empty ());
1099- }
1100- }
1101- }
11021166 }
11031167
11041168 zv::Arr markers = zv::Arr::empty ();
@@ -2002,7 +2066,7 @@ PT_MINIT_REGISTRATION(pt_register_closure_signature_inference)
20022066 pt_csi_closed_body_attribute = zend_string_init_interned (PT_LC (" closureSignatureClosedBody" ), 1 );
20032067 pt_csi_returns_context_typed_attribute = zend_string_init_interned (PT_LC (" closureSignatureReturnsContextTyped" ), 1 );
20042068 pt_csi_assigned_closures_attribute = zend_string_init_interned (PT_LC (" closureSignatureAssignedClosures" ), 1 );
2005- pt_csi_nested_assigned_closures_attribute = zend_string_init_interned (PT_LC (" closureSignatureNestedAssignedClosures " ), 1 );
2069+ pt_csi_assigned_to_by_ref_use_attribute = zend_string_init_interned (PT_LC (" closureSignatureAssignedToByRefUse " ), 1 );
20062070 pt_csi_invocation_template_name = zend_string_init_interned (PT_LC (" @invokes" ), 1 );
20072071 pt_csi_by_ref_uses_attribute = zend_string_init_interned (PT_LC (" closureSignatureByRefUses" ), 1 );
20082072 pt_csi_arrow_function_outer_variables_attribute = zend_string_init_interned (PT_LC (" closureSignatureArrowFunctionOuterVariables" ), 1 );
@@ -2020,7 +2084,7 @@ PT_MINIT_REGISTRATION(pt_register_closure_signature_inference)
20202084 cls.privateClassConstantString (" CLOSED_BODY_ATTRIBUTE" , " closureSignatureClosedBody" );
20212085 cls.privateClassConstantString (" ASSIGNED_CLOSURES_ATTRIBUTE" , " closureSignatureAssignedClosures" );
20222086 cls.privateClassConstantString (" REBOUND_VARIABLES_ATTRIBUTE" , " closureSignatureReboundVariables" );
2023- cls.privateClassConstantString (" NESTED_ASSIGNED_CLOSURES_ATTRIBUTE " , " closureSignatureNestedAssignedClosures " );
2087+ cls.privateClassConstantString (" ASSIGNED_TO_BY_REF_USE_ATTRIBUTE " , " closureSignatureAssignedToByRefUse " );
20242088
20252089 /* the real parameter types: the DI container autowires the service by
20262090 * reflecting the constructor */
0 commit comments