<URL: http://bugs.freeciv.org/Ticket/Display.html?id=39668 >

On 04/09/2007, Marko Lindqvist  wrote:
>
>   Calling script_signal_emit() means that almost anything can happen.
>  Most definitely cities or units can disappear.

 Actually, they can't disappear just yet. Scripting improvements allowing
city destruction and unit death are waiting for this ticket.

>   I just glanced where it has been called, and fixed most obvious cases
>  where some now potentially invalid pointer was used immediately after
>  script_signal_emit().

 Updated against current S2_2 svn. Untested, as #40166 prevented testing.


 - ML

diff -Nurd -X.diff_ignore freeciv/common/city.c freeciv/common/city.c
--- freeciv/common/city.c	2008-03-12 21:58:28.000000000 +0200
+++ freeciv/common/city.c	2008-03-24 16:14:28.000000000 +0200
@@ -2592,3 +2592,17 @@
   memset(pcity, 0, sizeof(*pcity)); /* ensure no pointers remain */
   free(pcity);
 }
+
+/**************************************************************************
+  Check if city with given id still exist. Use this before using
+  old city pointers when city might have disappeared.
+**************************************************************************/
+bool city_exist(int id)
+{
+  /* Check if city exist in game */
+  if (game_find_city_by_number(id)) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
diff -Nurd -X.diff_ignore freeciv/common/city.h freeciv/common/city.h
--- freeciv/common/city.h	2008-03-12 21:58:28.000000000 +0200
+++ freeciv/common/city.h	2008-03-24 15:40:04.000000000 +0200
@@ -610,6 +610,8 @@
 			 int *pollu_prod, int *pollu_pop, int *pollu_mod);
 int city_pollution(const struct city *pcity, int shield_total);
 
+bool city_exist(int id);
+
 /*
  * Iterates over all improvements, skipping those not yet built in the
  * given city.
diff -Nurd -X.diff_ignore freeciv/server/cityturn.c freeciv/server/cityturn.c
--- freeciv/server/cityturn.c	2008-03-12 21:58:27.000000000 +0200
+++ freeciv/server/cityturn.c	2008-03-24 16:01:55.000000000 +0200
@@ -584,6 +584,7 @@
   struct tile *pcenter = city_tile(pcity);
   struct player *powner = city_owner(pcity);
   struct impr_type *pimprove = pcity->production.value.building;
+  int saved_id = pcity->id;
 
   if (!city_can_grow_to(pcity, pcity->size + 1)) { /* need improvement */
     if (get_current_construction_bonus(pcity, EFT_SIZE_ADJ, RPT_CERTAIN) > 0
@@ -655,8 +656,10 @@
                 pcity->size);
   script_signal_emit("city_growth", 2,
 		      API_TYPE_CITY, pcity, API_TYPE_INT, pcity->size);
-
-  sanity_check_city(pcity);
+  if (city_exist(saved_id)) {
+    /* Script didn't destroy this city */
+    sanity_check_city(pcity);
+  }
   sync_cities();
 }
 
@@ -781,14 +784,30 @@
   struct universal target;
   bool success = FALSE;
   int i;
+  int saved_id = pcity->id;
+  bool city_checked = TRUE; /* This is used to avoid spurious city_exist() calls */
 
   if (worklist_is_empty(&pcity->worklist))
     /* Nothing in the worklist; bail now. */
     return FALSE;
 
   i = 0;
-  while (!success && worklist_peek_ith(&pcity->worklist, &target, i++)) {
-    success = can_city_build_now(pcity, target);
+  while (!success) {
+
+    if (!city_checked) {
+      if (!city_exist(saved_id)) {
+        /* Some script has removed useless city that cannot build
+         * what it is told to! */
+        return FALSE;
+      }
+      city_checked = TRUE;
+    }
+
+    if (worklist_peek_ith(&pcity->worklist, &target, i++)) {
+      success = can_city_build_now(pcity, target);
+    } else {
+      success = FALSE;
+    }
     if (success) {
       break; /* while */
     }
@@ -811,6 +830,7 @@
 			   API_TYPE_UNIT_TYPE, ptarget,
 			   API_TYPE_CITY, pcity,
 			   API_TYPE_STRING, "need_tech");
+        city_checked = FALSE;
 	break;
       }
       success = can_city_build_unit_later(pcity, pupdate);
@@ -829,8 +849,13 @@
 			   API_TYPE_UNIT_TYPE, ptarget,
 			   API_TYPE_CITY, pcity,
 			   API_TYPE_STRING, "never");
-	/* Purge this worklist item. */
-	worklist_remove(&pcity->worklist, --i);
+        if (city_exist(saved_id)) {
+          city_checked = TRUE;
+          /* Purge this worklist item. */
+          worklist_remove(&pcity->worklist, --i);
+        } else {
+          city_checked = FALSE;
+        }
       } else {
 	/* Yep, we can go after pupdate instead.  Joy! */
 	notify_player(pplayer, pcity->tile, E_WORKLIST,
@@ -860,9 +885,13 @@
 			   API_TYPE_BUILDING_TYPE, ptarget,
 			   API_TYPE_CITY, pcity,
 			   API_TYPE_STRING, "never");
-
-	/* Purge this worklist item. */
-	worklist_remove(&pcity->worklist, --i);
+        if (city_exist(saved_id)) {
+          city_checked = TRUE;
+          /* Purge this worklist item. */
+          worklist_remove(&pcity->worklist, --i);
+        } else {
+          city_checked = FALSE;
+        }
 	break;
       }
 
@@ -1012,7 +1041,16 @@
 	    };
 	    break;
 	  }
+
+          /* Almost all cases emit signal in the end, so city check needed. */
+          if (!city_exist(saved_id)) {
+            /* Some script has removed city */
+            return FALSE;
+          }
+          city_checked = TRUE;
+
 	} requirement_vector_iterate_end;
+
 	if (!known) {
 	  /* This shouldn't happen...
 	     FIXME: make can_city_build_improvement_now() return a reason enum. */
@@ -1252,6 +1290,7 @@
   bool space_part;
   int mod;
   struct impr_type *pimprove = pcity->production.value.building;
+  int saved_id = pcity->id;
 
   if (city_production_has_flag(pcity, IF_GOLD)) {
     assert(pcity->surplus[O_SHIELD] >= 0);
@@ -1326,6 +1365,11 @@
 		       API_TYPE_BUILDING_TYPE, pimprove,
 		       API_TYPE_CITY, pcity);
 
+    if (!city_exist(saved_id)) {
+      /* Script removed city */
+      return FALSE;
+    }
+
     /* Call this function since some buildings may change the
      * the vision range of a city */
     city_refresh_vision(pcity);
@@ -1390,19 +1434,22 @@
         _("%s is building %s, which is no longer available."),
         city_name(pcity),
         utype_name_translation(utype));
-    script_signal_emit("unit_cant_be_built", 3,
-		       API_TYPE_UNIT_TYPE, utype,
-		       API_TYPE_CITY, pcity,
-		       API_TYPE_STRING, "unavailable");
+
+    /* Log before signal emitting, so pointers are certainly valid */
     freelog(LOG_VERBOSE, "%s %s tried to build %s, which is not available.",
             nation_rule_name(nation_of_city(pcity)),
             city_name(pcity),
             utype_rule_name(utype));
+    script_signal_emit("unit_cant_be_built", 3,
+		       API_TYPE_UNIT_TYPE, utype,
+		       API_TYPE_CITY, pcity,
+		       API_TYPE_STRING, "unavailable");
     return TRUE;
   }
   if (pcity->shield_stock >= utype_build_shield_cost(utype)) {
     int pop_cost = utype_pop_value(utype);
     struct unit *punit;
+    int saved_city_id = pcity->id;
 
     /* Should we disband the city? -- Massimo */
     if (pcity->size == pop_cost
@@ -1452,8 +1499,10 @@
     script_signal_emit("unit_built",
 		       2, API_TYPE_UNIT, punit, API_TYPE_CITY, pcity);
 
-    /* Done building this unit; time to move on to the next. */
-    choose_build_target(pplayer, pcity);
+    if (city_exist(saved_city_id)) {
+      /* Done building this unit; time to move on to the next. */
+      choose_build_target(pplayer, pcity);
+    }
   }
   return TRUE;
 }
@@ -1746,6 +1795,7 @@
   struct tile *ptile = pcity->tile;
   struct city *rcity=NULL;
   struct unit_type *utype = pcity->production.value.utype;
+  int saved_id = pcity->id;
 
   /* find closest city other than pcity */
   rcity = find_closest_owned_city(pplayer, ptile, FALSE, pcity);
@@ -1761,7 +1811,12 @@
 		       API_TYPE_UNIT_TYPE, utype,
 		       API_TYPE_CITY, pcity,
 		       API_TYPE_STRING, "pop_cost");
-    return FALSE;
+    if (!city_exist(saved_id)) {
+      /* Script decided to remove even the last city */
+      return TRUE;
+    } else {
+      return FALSE;
+    }
   }
 
   (void) create_unit(pplayer, ptile, utype,
diff -Nurd -X.diff_ignore freeciv/server/unittools.c freeciv/server/unittools.c
--- freeciv/server/unittools.c	2008-03-12 21:58:28.000000000 +0200
+++ freeciv/server/unittools.c	2008-03-24 16:06:24.000000000 +0200
@@ -2255,7 +2255,9 @@
 {
   struct player *pplayer = unit_owner(punit);
   enum hut_behavior behavior = unit_class(punit)->hut_behavior;
-  
+
+  /* FIXME: Should we still run "hut_enter" script when
+   *        hut_behavior is HUT_NOTHING or HUT_FRIGHTEN? */ 
   if (behavior == HUT_NOTHING) {
     return;
   }
@@ -2642,6 +2644,7 @@
   struct unit *ptransporter = NULL;
   struct vision *old_vision = punit->server.vision;
   struct vision *new_vision;
+  int saved_id = punit->id;
     
   conn_list_do_buffer(pplayer->connections);
 
@@ -2811,10 +2814,17 @@
   } square_iterate_end;
 
   unit_move_consequences(punit, psrctile, pdesttile);
+
+  /* FIXME: Should signal emit be after sentried units have been
+   *        waken up in case script causes unit death. */
   script_signal_emit("unit_moved", 3,
 		     API_TYPE_UNIT, punit,
 		     API_TYPE_TILE, psrctile,
 		     API_TYPE_TILE, pdesttile);
+  if (!unit_alive(saved_id)) {
+    /* Script caused unit to die */
+    return FALSE;
+  }
   wakeup_neighbor_sentries(punit);
   if (!unit_survive_autoattack(punit)) {
     conn_list_do_unbuffer(pplayer->connections);
_______________________________________________
Freeciv-dev mailing list
Freeciv-dev@gna.org
https://mail.gna.org/listinfo/freeciv-dev

Reply via email to