@@ -501,8 +501,39 @@ void phpdbg_free_watch_element(phpdbg_watch_element *element);
501501void phpdbg_remove_watchpoint (phpdbg_watchpoint_t * watch );
502502void phpdbg_watch_parent_ht (phpdbg_watch_element * element );
503503
504- phpdbg_watch_element * phpdbg_add_watch_element (phpdbg_watchpoint_t * watch , phpdbg_watch_element * element ) {
504+ static bool phpdbg_watch_element_collides (void * addr , phpdbg_watchtype type , phpdbg_watch_element * element ) {
505+ phpdbg_watch_element * old ;
506+ phpdbg_btree_result * res = phpdbg_btree_find (& PHPDBG_G (watchpoint_tree ), (zend_ulong ) addr );
507+
508+ if (res ) {
509+ phpdbg_watchpoint_t * watch = res -> ptr ;
510+ if (watch -> type == type && (old = zend_hash_find_ptr (& watch -> elements , element -> str )) && old != element ) {
511+ return true;
512+ }
513+ }
514+
515+ return (old = zend_hash_find_ptr (& PHPDBG_G (watch_recreation ), element -> str )) && old != element ;
516+ }
517+
518+ static bool phpdbg_ht_watch_element_collides (zval * zv , phpdbg_watch_element * element ) {
519+ HashTable * ht = HT_FROM_ZVP (zv );
520+
521+ if (!ht ) {
522+ return false;
523+ }
524+
525+ return phpdbg_watch_element_collides (HT_WATCH_OFFSET + (char * ) ht , WATCH_ON_HASHTABLE , element );
526+ }
527+
528+ static bool phpdbg_bucket_watch_element_collides (zval * zv , phpdbg_watch_element * element ) {
529+ return phpdbg_watch_element_collides ((Bucket * ) zv , WATCH_ON_BUCKET , element );
530+ }
531+
532+ phpdbg_watch_element * phpdbg_add_watch_element (phpdbg_watchpoint_t * watch , phpdbg_watch_element * element , bool * is_new ) {
505533 phpdbg_btree_result * res ;
534+ if (is_new ) {
535+ * is_new = true;
536+ }
506537 if ((res = phpdbg_btree_find (& PHPDBG_G (watchpoint_tree ), (zend_ulong ) watch -> addr .ptr )) == NULL ) {
507538 phpdbg_watchpoint_t * mem = emalloc (sizeof (* mem ));
508539 * mem = * watch ;
@@ -520,6 +551,9 @@ phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdb
520551 if (element != old_element ) {
521552 phpdbg_free_watch_element (element );
522553 }
554+ if (is_new ) {
555+ * is_new = false;
556+ }
523557 return old_element ;
524558 }
525559 }
@@ -534,25 +568,35 @@ phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdb
534568 return element ;
535569}
536570
537- phpdbg_watch_element * phpdbg_add_bucket_watch_element (Bucket * bucket , phpdbg_watch_element * element ) {
571+ phpdbg_watch_element * phpdbg_add_bucket_watch_element (Bucket * bucket , phpdbg_watch_element * element , bool * is_new ) {
538572 phpdbg_watchpoint_t watch ;
539573 phpdbg_set_bucket_watchpoint (bucket , & watch );
540- element = phpdbg_add_watch_element (& watch , element );
541- phpdbg_watch_parent_ht (element );
542- return element ;
574+ bool added_new ;
575+ phpdbg_watch_element * added = phpdbg_add_watch_element (& watch , element , & added_new );
576+ if (added_new ) {
577+ phpdbg_watch_parent_ht (added );
578+ }
579+ if (is_new ) {
580+ * is_new = added_new ;
581+ }
582+ return added ;
543583}
544584
545- phpdbg_watch_element * phpdbg_add_ht_watch_element (zval * zv , phpdbg_watch_element * element ) {
585+ phpdbg_watch_element * phpdbg_add_ht_watch_element (zval * zv , phpdbg_watch_element * element , bool * is_new ) {
546586 phpdbg_watchpoint_t watch ;
547587 HashTable * ht = HT_FROM_ZVP (zv );
548588
589+ if (is_new ) {
590+ * is_new = false;
591+ }
592+
549593 if (!ht ) {
550594 return NULL ;
551595 }
552596
553597 element -> flags |= Z_TYPE_P (zv ) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT ;
554598 phpdbg_set_ht_watchpoint (ht , & watch );
555- return phpdbg_add_watch_element (& watch , element );
599+ return phpdbg_add_watch_element (& watch , element , is_new );
556600}
557601
558602bool phpdbg_is_recursively_watched (void * ptr , phpdbg_watch_element * element ) {
@@ -588,8 +632,15 @@ void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long
588632 child -> parent = element ;
589633 child -> child = NULL ;
590634 child -> parent_container = HT_WATCH_HT (element -> watch );
591- zend_hash_add_ptr (& element -> child_container , child -> str , child );
592- phpdbg_add_bucket_watch_element ((Bucket * ) zv , child );
635+ if (phpdbg_bucket_watch_element_collides (zv , child )) {
636+ phpdbg_free_watch_element (child );
637+ return ;
638+ }
639+ bool added_new ;
640+ phpdbg_add_bucket_watch_element ((Bucket * ) zv , child , & added_new );
641+ if (added_new ) {
642+ zend_hash_add_ptr (& element -> child_container , child -> str , child );
643+ }
593644}
594645
595646void phpdbg_recurse_watch_element (phpdbg_watch_element * element ) {
@@ -627,8 +678,13 @@ void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
627678 child -> child = NULL ;
628679 element -> child = child ;
629680 }
681+ if (phpdbg_ht_watch_element_collides (zv , child )) {
682+ phpdbg_free_watch_element (child );
683+ element -> child = NULL ;
684+ return ;
685+ }
630686 zend_hash_init (& child -> child_container , 8 , NULL , NULL , 0 );
631- phpdbg_add_ht_watch_element (zv , child );
687+ phpdbg_add_ht_watch_element (zv , child , NULL );
632688 } else if (zend_hash_num_elements (& element -> child_container ) == 0 ) {
633689 zend_string * str ;
634690 zend_long idx ;
@@ -727,7 +783,10 @@ bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *elem
727783 phpdbg_print_watch_diff (WATCH_ON_HASHTABLE , element -> str , oldPtr , htPtr );
728784 }
729785
730- phpdbg_add_ht_watch_element (parent , element );
786+ if ((element -> flags & PHPDBG_WATCH_RECURSIVE ) && phpdbg_ht_watch_element_collides (parent , element )) {
787+ return false;
788+ }
789+ element = phpdbg_add_ht_watch_element (parent , element , NULL );
731790 } else if ((zv = zend_symtable_find (ht , element -> name_in_parent ))) {
732791 if (element -> flags & PHPDBG_WATCH_IMPLICIT ) {
733792 zval * next = zv ;
@@ -746,9 +805,11 @@ bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *elem
746805 phpdbg_print_watch_diff (WATCH_ON_ZVAL , element -> str , & element -> backup .zv , zv );
747806 }
748807
808+ if ((element -> flags & PHPDBG_WATCH_RECURSIVE ) && phpdbg_bucket_watch_element_collides (zv , element )) {
809+ return false;
810+ }
749811 element -> parent_container = ht ;
750- phpdbg_add_bucket_watch_element ((Bucket * ) zv , element );
751- phpdbg_watch_parent_ht (element );
812+ element = phpdbg_add_bucket_watch_element ((Bucket * ) zv , element , NULL );
752813 } else {
753814 return false;
754815 }
@@ -1248,63 +1309,106 @@ void phpdbg_list_watchpoints(void) {
12481309 } ZEND_HASH_FOREACH_END ();
12491310}
12501311
1251- static int phpdbg_create_simple_watchpoint (zval * zv , phpdbg_watch_element * element ) {
1252- element -> flags = PHPDBG_WATCH_SIMPLE ;
1253- phpdbg_add_bucket_watch_element ((Bucket * ) zv , element );
1254- return SUCCESS ;
1312+ typedef enum {
1313+ PHPDBG_WATCH_ADD_OK ,
1314+ PHPDBG_WATCH_ADD_FAILED ,
1315+ PHPDBG_WATCH_ADD_DUPLICATE ,
1316+ } phpdbg_watch_add_result ;
1317+
1318+ static phpdbg_watch_add_result phpdbg_create_simple_watchpoint (zval * zv , phpdbg_watch_element * * element ) {
1319+ phpdbg_watch_element * added ;
1320+ (* element )-> flags = PHPDBG_WATCH_SIMPLE ;
1321+ bool added_new ;
1322+ added = phpdbg_add_bucket_watch_element ((Bucket * ) zv , * element , & added_new );
1323+ if (!added_new ) {
1324+ * element = added ;
1325+ return PHPDBG_WATCH_ADD_DUPLICATE ;
1326+ }
1327+ return PHPDBG_WATCH_ADD_OK ;
12551328}
12561329
1257- static int phpdbg_create_array_watchpoint (zval * zv , phpdbg_watch_element * element ) {
1258- phpdbg_watch_element * new ;
1330+ static phpdbg_watch_add_result phpdbg_create_array_watchpoint (zval * zv , phpdbg_watch_element * * element_ptr ) {
1331+ phpdbg_watch_element * element = * element_ptr ;
1332+ phpdbg_watch_element * new , * added ;
12591333 zend_string * str ;
12601334 zval * orig_zv = zv ;
12611335
12621336 ZVAL_DEREF (zv );
12631337 if (Z_TYPE_P (zv ) != IS_ARRAY && Z_TYPE_P (zv ) != IS_OBJECT ) {
1264- return FAILURE ;
1338+ return PHPDBG_WATCH_ADD_FAILED ;
12651339 }
12661340
1267- new = ecalloc (1 , sizeof (phpdbg_watch_element ));
1268-
12691341 str = strpprintf (0 , "%.*s[]" , (int ) ZSTR_LEN (element -> str ), ZSTR_VAL (element -> str ));
12701342 zend_string_release (element -> str );
12711343 element -> str = str ;
12721344 element -> flags = PHPDBG_WATCH_IMPLICIT ;
1273- phpdbg_add_bucket_watch_element ((Bucket * ) orig_zv , element );
1274- element -> child = new ;
1345+ bool added_new ;
1346+ added = phpdbg_add_bucket_watch_element ((Bucket * ) orig_zv , element , & added_new );
1347+ if (!added_new ) {
1348+ * element_ptr = added ;
1349+ return PHPDBG_WATCH_ADD_DUPLICATE ;
1350+ }
1351+ element = added ;
12751352
1353+ new = ecalloc (1 , sizeof (phpdbg_watch_element ));
12761354 new -> flags = PHPDBG_WATCH_SIMPLE ;
12771355 new -> str = zend_string_copy (str );
12781356 new -> parent = element ;
1279- phpdbg_add_ht_watch_element (zv , new );
1280- return SUCCESS ;
1357+ added = phpdbg_add_ht_watch_element (zv , new , & added_new );
1358+ if (added != NULL && !added_new ) {
1359+ phpdbg_clean_watch_element (element );
1360+ phpdbg_free_watch_element (element );
1361+ * element_ptr = added ;
1362+ return PHPDBG_WATCH_ADD_DUPLICATE ;
1363+ }
1364+ element -> child = new ;
1365+ * element_ptr = element ;
1366+ return PHPDBG_WATCH_ADD_OK ;
12811367}
12821368
1283- static int phpdbg_create_recursive_watchpoint (zval * zv , phpdbg_watch_element * element ) {
1284- element -> flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT ;
1285- element -> child = NULL ;
1286- phpdbg_add_bucket_watch_element ((Bucket * ) zv , element );
1287- return SUCCESS ;
1369+ static phpdbg_watch_add_result phpdbg_create_recursive_watchpoint (zval * zv , phpdbg_watch_element * * element ) {
1370+ phpdbg_watch_element * added ;
1371+ (* element )-> flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT ;
1372+ (* element )-> child = NULL ;
1373+ bool added_new ;
1374+ added = phpdbg_add_bucket_watch_element ((Bucket * ) zv , * element , & added_new );
1375+ if (!added_new ) {
1376+ * element = added ;
1377+ return PHPDBG_WATCH_ADD_DUPLICATE ;
1378+ }
1379+ return PHPDBG_WATCH_ADD_OK ;
12881380}
12891381
1290- typedef struct { int (* callback )(zval * zv , phpdbg_watch_element * ); zend_string * str ; } phpdbg_watch_parse_struct ;
1382+ typedef struct { phpdbg_watch_add_result (* callback )(zval * zv , phpdbg_watch_element * * ); zend_string * str ; } phpdbg_watch_parse_struct ;
12911383
12921384static int phpdbg_watchpoint_parse_wrapper (char * name , size_t namelen , char * key , size_t keylen , HashTable * parent , zval * zv , phpdbg_watch_parse_struct * info ) {
1293- int ret ;
1385+ int ret = SUCCESS ;
12941386 phpdbg_watch_element * element = ecalloc (1 , sizeof (phpdbg_watch_element ));
12951387 element -> str = zend_string_init (name , namelen , 0 );
12961388 element -> name_in_parent = zend_string_init (key , keylen , 0 );
12971389 element -> parent_container = parent ;
12981390 element -> parent = PHPDBG_G (watch_tmp );
12991391 element -> child = NULL ;
13001392
1301- ret = info -> callback (zv , element );
1393+ phpdbg_watch_add_result result = info -> callback (zv , & element );
13021394
13031395 efree (name );
13041396 efree (key );
13051397
1306- if (ret != SUCCESS ) {
1398+ if (result == PHPDBG_WATCH_ADD_FAILED ) {
13071399 phpdbg_remove_watch_element (element );
1400+ ret = FAILURE ;
1401+ } else if (result == PHPDBG_WATCH_ADD_DUPLICATE ) {
1402+ phpdbg_notice ("%.*s is already being watched" , (int ) ZSTR_LEN (element -> str ), ZSTR_VAL (element -> str ));
1403+ while (PHPDBG_G (watch_tmp ) && PHPDBG_G (watch_tmp )-> child == NULL ) {
1404+ phpdbg_watch_element * parent = PHPDBG_G (watch_tmp )-> parent ;
1405+ phpdbg_clean_watch_element (PHPDBG_G (watch_tmp ));
1406+ phpdbg_free_watch_element (PHPDBG_G (watch_tmp ));
1407+ if (parent ) {
1408+ parent -> child = NULL ;
1409+ }
1410+ PHPDBG_G (watch_tmp ) = parent ;
1411+ }
13081412 } else {
13091413 if (PHPDBG_G (watch_tmp )) {
13101414 PHPDBG_G (watch_tmp )-> child = element ;
@@ -1346,7 +1450,7 @@ static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, s
13461450 element -> name_in_parent = zend_string_init (key , keylen , 0 );
13471451 element -> parent_container = parent ;
13481452 element -> parent = PHPDBG_G (watch_tmp );
1349- element = phpdbg_add_bucket_watch_element ((Bucket * ) zv , element );
1453+ element = phpdbg_add_bucket_watch_element ((Bucket * ) zv , element , NULL );
13501454
13511455 efree (name );
13521456 efree (key );
@@ -1359,7 +1463,7 @@ static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, s
13591463 return SUCCESS ;
13601464}
13611465
1362- static int phpdbg_watchpoint_parse_symtables (char * input , size_t len , int (* callback )(zval * , phpdbg_watch_element * )) {
1466+ static int phpdbg_watchpoint_parse_symtables (char * input , size_t len , phpdbg_watch_add_result (* callback )(zval * , phpdbg_watch_element * * )) {
13631467 zend_class_entry * scope = zend_get_executed_scope ();
13641468 phpdbg_watch_parse_struct info ;
13651469 int ret ;
@@ -1529,6 +1633,37 @@ void phpdbg_destroy_watchpoints(void) {
15291633 free (PHPDBG_G (watchlist_mem_backup ));
15301634}
15311635
1636+ void phpdbg_release_watch_elements (void ) {
1637+ phpdbg_watch_element * element ;
1638+ uint32_t guard ;
1639+
1640+ ZEND_HASH_MAP_FOREACH_PTR (& PHPDBG_G (watch_recreation ), element ) {
1641+ phpdbg_automatic_dequeue_free (element );
1642+ } ZEND_HASH_FOREACH_END ();
1643+ zend_hash_clean (& PHPDBG_G (watch_recreation ));
1644+
1645+ guard = zend_hash_num_elements (& PHPDBG_G (watch_elements )) + 1 ;
1646+ while (zend_hash_num_elements (& PHPDBG_G (watch_elements )) > 0 && guard -- > 0 ) {
1647+ ZEND_HASH_FOREACH_PTR (& PHPDBG_G (watch_elements ), element ) {
1648+ phpdbg_remove_watch_element (element );
1649+ break ;
1650+ } ZEND_HASH_FOREACH_END ();
1651+ }
1652+ zend_hash_clean (& PHPDBG_G (watch_recreation ));
1653+
1654+ phpdbg_btree_clean (& PHPDBG_G (watchpoint_tree ));
1655+ phpdbg_btree_clean (& PHPDBG_G (watch_HashTables ));
1656+
1657+ zend_hash_destroy (& PHPDBG_G (watch_elements ));
1658+ zend_hash_init (& PHPDBG_G (watch_elements ), 8 , NULL , NULL , 0 );
1659+ zend_hash_destroy (& PHPDBG_G (watch_recreation ));
1660+ zend_hash_init (& PHPDBG_G (watch_recreation ), 8 , NULL , NULL , 0 );
1661+ zend_hash_destroy (& PHPDBG_G (watch_free ));
1662+ zend_hash_init (& PHPDBG_G (watch_free ), 8 , NULL , NULL , 0 );
1663+ zend_hash_destroy (& PHPDBG_G (watch_collisions ));
1664+ zend_hash_init (& PHPDBG_G (watch_collisions ), 8 , NULL , NULL , 0 );
1665+ }
1666+
15321667void phpdbg_purge_watchpoint_tree (void ) {
15331668 phpdbg_btree_position pos ;
15341669 phpdbg_btree_result * res ;
0 commit comments