@@ -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 }
@@ -1010,7 +1071,8 @@ void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
10101071 zend_string * name = NULL ;
10111072 void * comparePtr ;
10121073
1013- if (watch -> type == WATCH_ON_HASHTABLE ) {
1074+ if (watch -> type == WATCH_ON_HASHTABLE
1075+ && phpdbg_check_watch_diff (WATCH_ON_HASHTABLE , (char * ) & watch -> backup .ht + HT_WATCH_OFFSET , watch -> addr .ptr )) {
10141076 phpdbg_watch_element * element ;
10151077 zend_string * str ;
10161078 zend_long idx ;
@@ -1248,63 +1310,106 @@ void phpdbg_list_watchpoints(void) {
12481310 } ZEND_HASH_FOREACH_END ();
12491311}
12501312
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 ;
1313+ typedef enum {
1314+ PHPDBG_WATCH_ADD_OK ,
1315+ PHPDBG_WATCH_ADD_FAILED ,
1316+ PHPDBG_WATCH_ADD_DUPLICATE ,
1317+ } phpdbg_watch_add_result ;
1318+
1319+ static phpdbg_watch_add_result phpdbg_create_simple_watchpoint (zval * zv , phpdbg_watch_element * * element ) {
1320+ phpdbg_watch_element * added ;
1321+ (* element )-> flags = PHPDBG_WATCH_SIMPLE ;
1322+ bool added_new ;
1323+ added = phpdbg_add_bucket_watch_element ((Bucket * ) zv , * element , & added_new );
1324+ if (!added_new ) {
1325+ * element = added ;
1326+ return PHPDBG_WATCH_ADD_DUPLICATE ;
1327+ }
1328+ return PHPDBG_WATCH_ADD_OK ;
12551329}
12561330
1257- static int phpdbg_create_array_watchpoint (zval * zv , phpdbg_watch_element * element ) {
1258- phpdbg_watch_element * new ;
1331+ static phpdbg_watch_add_result phpdbg_create_array_watchpoint (zval * zv , phpdbg_watch_element * * element_ptr ) {
1332+ phpdbg_watch_element * element = * element_ptr ;
1333+ phpdbg_watch_element * new , * added ;
12591334 zend_string * str ;
12601335 zval * orig_zv = zv ;
12611336
12621337 ZVAL_DEREF (zv );
12631338 if (Z_TYPE_P (zv ) != IS_ARRAY && Z_TYPE_P (zv ) != IS_OBJECT ) {
1264- return FAILURE ;
1339+ return PHPDBG_WATCH_ADD_FAILED ;
12651340 }
12661341
1267- new = ecalloc (1 , sizeof (phpdbg_watch_element ));
1268-
12691342 str = strpprintf (0 , "%.*s[]" , (int ) ZSTR_LEN (element -> str ), ZSTR_VAL (element -> str ));
12701343 zend_string_release (element -> str );
12711344 element -> str = str ;
12721345 element -> flags = PHPDBG_WATCH_IMPLICIT ;
1273- phpdbg_add_bucket_watch_element ((Bucket * ) orig_zv , element );
1274- element -> child = new ;
1346+ bool added_new ;
1347+ added = phpdbg_add_bucket_watch_element ((Bucket * ) orig_zv , element , & added_new );
1348+ if (!added_new ) {
1349+ * element_ptr = added ;
1350+ return PHPDBG_WATCH_ADD_DUPLICATE ;
1351+ }
1352+ element = added ;
12751353
1354+ new = ecalloc (1 , sizeof (phpdbg_watch_element ));
12761355 new -> flags = PHPDBG_WATCH_SIMPLE ;
12771356 new -> str = zend_string_copy (str );
12781357 new -> parent = element ;
1279- phpdbg_add_ht_watch_element (zv , new );
1280- return SUCCESS ;
1358+ added = phpdbg_add_ht_watch_element (zv , new , & added_new );
1359+ if (added != NULL && !added_new ) {
1360+ phpdbg_clean_watch_element (element );
1361+ phpdbg_free_watch_element (element );
1362+ * element_ptr = added ;
1363+ return PHPDBG_WATCH_ADD_DUPLICATE ;
1364+ }
1365+ element -> child = new ;
1366+ * element_ptr = element ;
1367+ return PHPDBG_WATCH_ADD_OK ;
12811368}
12821369
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 ;
1370+ static phpdbg_watch_add_result phpdbg_create_recursive_watchpoint (zval * zv , phpdbg_watch_element * * element ) {
1371+ phpdbg_watch_element * added ;
1372+ (* element )-> flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT ;
1373+ (* element )-> child = NULL ;
1374+ bool added_new ;
1375+ added = phpdbg_add_bucket_watch_element ((Bucket * ) zv , * element , & added_new );
1376+ if (!added_new ) {
1377+ * element = added ;
1378+ return PHPDBG_WATCH_ADD_DUPLICATE ;
1379+ }
1380+ return PHPDBG_WATCH_ADD_OK ;
12881381}
12891382
1290- typedef struct { int (* callback )(zval * zv , phpdbg_watch_element * ); zend_string * str ; } phpdbg_watch_parse_struct ;
1383+ typedef struct { phpdbg_watch_add_result (* callback )(zval * zv , phpdbg_watch_element * * ); zend_string * str ; } phpdbg_watch_parse_struct ;
12911384
12921385static 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 ;
1386+ int ret = SUCCESS ;
12941387 phpdbg_watch_element * element = ecalloc (1 , sizeof (phpdbg_watch_element ));
12951388 element -> str = zend_string_init (name , namelen , 0 );
12961389 element -> name_in_parent = zend_string_init (key , keylen , 0 );
12971390 element -> parent_container = parent ;
12981391 element -> parent = PHPDBG_G (watch_tmp );
12991392 element -> child = NULL ;
13001393
1301- ret = info -> callback (zv , element );
1394+ phpdbg_watch_add_result result = info -> callback (zv , & element );
13021395
13031396 efree (name );
13041397 efree (key );
13051398
1306- if (ret != SUCCESS ) {
1399+ if (result == PHPDBG_WATCH_ADD_FAILED ) {
13071400 phpdbg_remove_watch_element (element );
1401+ ret = FAILURE ;
1402+ } else if (result == PHPDBG_WATCH_ADD_DUPLICATE ) {
1403+ phpdbg_notice ("%.*s is already being watched" , (int ) ZSTR_LEN (element -> str ), ZSTR_VAL (element -> str ));
1404+ while (PHPDBG_G (watch_tmp ) && PHPDBG_G (watch_tmp )-> child == NULL ) {
1405+ phpdbg_watch_element * parent = PHPDBG_G (watch_tmp )-> parent ;
1406+ phpdbg_clean_watch_element (PHPDBG_G (watch_tmp ));
1407+ phpdbg_free_watch_element (PHPDBG_G (watch_tmp ));
1408+ if (parent ) {
1409+ parent -> child = NULL ;
1410+ }
1411+ PHPDBG_G (watch_tmp ) = parent ;
1412+ }
13081413 } else {
13091414 if (PHPDBG_G (watch_tmp )) {
13101415 PHPDBG_G (watch_tmp )-> child = element ;
@@ -1346,7 +1451,7 @@ static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, s
13461451 element -> name_in_parent = zend_string_init (key , keylen , 0 );
13471452 element -> parent_container = parent ;
13481453 element -> parent = PHPDBG_G (watch_tmp );
1349- element = phpdbg_add_bucket_watch_element ((Bucket * ) zv , element );
1454+ element = phpdbg_add_bucket_watch_element ((Bucket * ) zv , element , NULL );
13501455
13511456 efree (name );
13521457 efree (key );
@@ -1359,7 +1464,7 @@ static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, s
13591464 return SUCCESS ;
13601465}
13611466
1362- static int phpdbg_watchpoint_parse_symtables (char * input , size_t len , int (* callback )(zval * , phpdbg_watch_element * )) {
1467+ static int phpdbg_watchpoint_parse_symtables (char * input , size_t len , phpdbg_watch_add_result (* callback )(zval * , phpdbg_watch_element * * )) {
13631468 zend_class_entry * scope = zend_get_executed_scope ();
13641469 phpdbg_watch_parse_struct info ;
13651470 int ret ;
@@ -1529,6 +1634,37 @@ void phpdbg_destroy_watchpoints(void) {
15291634 free (PHPDBG_G (watchlist_mem_backup ));
15301635}
15311636
1637+ void phpdbg_release_watch_elements (void ) {
1638+ phpdbg_watch_element * element ;
1639+ uint32_t guard ;
1640+
1641+ ZEND_HASH_MAP_FOREACH_PTR (& PHPDBG_G (watch_recreation ), element ) {
1642+ phpdbg_automatic_dequeue_free (element );
1643+ } ZEND_HASH_FOREACH_END ();
1644+ zend_hash_clean (& PHPDBG_G (watch_recreation ));
1645+
1646+ guard = zend_hash_num_elements (& PHPDBG_G (watch_elements )) + 1 ;
1647+ while (zend_hash_num_elements (& PHPDBG_G (watch_elements )) > 0 && guard -- > 0 ) {
1648+ ZEND_HASH_FOREACH_PTR (& PHPDBG_G (watch_elements ), element ) {
1649+ phpdbg_remove_watch_element (element );
1650+ break ;
1651+ } ZEND_HASH_FOREACH_END ();
1652+ }
1653+ zend_hash_clean (& PHPDBG_G (watch_recreation ));
1654+
1655+ phpdbg_btree_clean (& PHPDBG_G (watchpoint_tree ));
1656+ phpdbg_btree_clean (& PHPDBG_G (watch_HashTables ));
1657+
1658+ zend_hash_destroy (& PHPDBG_G (watch_elements ));
1659+ zend_hash_init (& PHPDBG_G (watch_elements ), 8 , NULL , NULL , 0 );
1660+ zend_hash_destroy (& PHPDBG_G (watch_recreation ));
1661+ zend_hash_init (& PHPDBG_G (watch_recreation ), 8 , NULL , NULL , 0 );
1662+ zend_hash_destroy (& PHPDBG_G (watch_free ));
1663+ zend_hash_init (& PHPDBG_G (watch_free ), 8 , NULL , NULL , 0 );
1664+ zend_hash_destroy (& PHPDBG_G (watch_collisions ));
1665+ zend_hash_init (& PHPDBG_G (watch_collisions ), 8 , NULL , NULL , 0 );
1666+ }
1667+
15321668void phpdbg_purge_watchpoint_tree (void ) {
15331669 phpdbg_btree_position pos ;
15341670 phpdbg_btree_result * res ;
0 commit comments