From 2d4017d8e633a02843623eb7fdd94e253a9e3171 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C5=82awomir=20Lach?= <slawek@lach.art.pl>
Date: Mon, 27 Feb 2023 17:58:10 +0100
Subject: [PATCH] =?UTF-8?q?!OSDN:=20TICKET:=20#45428:=20S=C5=82awomir=20La?=
 =?UTF-8?q?ch=20<slawek@lach.art.pl>?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Duplicated names in ruleset causes error (on load).

diff --git a/server/ruleset.c b/server/ruleset.c
index 69698bd655..1358885399 100644
--- a/server/ruleset.c
+++ b/server/ruleset.c
@@ -208,6 +208,12 @@ static bool load_ruleset_veteran(struct section_file *file,
 char *script_buffer = NULL;
 char *parser_buffer = NULL;
 
+#define SPECHASH_TAG names_cache
+#define SPECHASH_CSTR_KEY_TYPE
+#define SPECHASH_CSTR_DATA_TYPE
+#include "spechash.h"
+
+
 /**********************************************************************//**
   Notifications about ruleset errors to clients. Especially important in
   case of internal server crashing.
@@ -1249,8 +1255,10 @@ static bool lookup_time(const struct section_file *secfile, int *turns,
 static bool ruleset_load_names(struct name_translation *pname,
                                const char *domain,
                                struct section_file *file,
+                               struct names_cache_hash *loaded,
                                const char *sec_name)
 {
+  char *previos_entry;
   const char *name = secfile_lookup_str(file, "%s.name", sec_name);
   const char *rule_name = secfile_lookup_str(file, "%s.rule_name", sec_name);
 
@@ -1261,6 +1269,17 @@ static bool ruleset_load_names(struct name_translation *pname,
     return FALSE;
   }
 
+  if (NULL != loaded) {
+    if (names_cache_hash_lookup(loaded, name, &previos_entry)) {
+      ruleset_error(NULL, LOG_ERROR,
+                  "\"%s\" [%s]: \"name\" field duplicated with \"%s\".",
+                  secfile_name(file), sec_name, previos_entry);
+      return FALSE;
+    }
+
+    names_cache_hash_insert(loaded, name, sec_name);
+  }
+
   names_set(pname, domain, name, rule_name);
 
   return TRUE;
@@ -1311,6 +1330,7 @@ static void ruleset_load_traits(struct trait_limits *out,
 static bool load_game_names(struct section_file *file,
                             struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   struct section_list *sec;
   int nval;
   const char *filename = secfile_name(file);
@@ -1339,10 +1359,12 @@ static bool load_game_names(struct section_file *file,
   }
 
   if (ok) {
+    names_hash = names_cache_hash_new();
     achievements_iterate(pach) {
       const char *sec_name = section_name(section_list_get(sec, achievement_index(pach)));
 
-      if (!ruleset_load_names(&pach->name, NULL, file, sec_name)) {
+      if (!ruleset_load_names(&pach->name, NULL, file, names_hash,
+                              sec_name)) {
         ruleset_error(NULL, LOG_ERROR,
                       "\"%s\": Cannot load achievement names",
                       filename);
@@ -1350,6 +1372,7 @@ static bool load_game_names(struct section_file *file,
         break;
       }
     } achievements_iterate_end;
+    names_cache_hash_destroy(names_hash);
   }
 
   section_list_destroy(sec);
@@ -1377,11 +1400,13 @@ static bool load_game_names(struct section_file *file,
     }
 
     if (ok) {
+      names_hash = names_cache_hash_new();
       goods_type_iterate(pgood) {
         const char *sec_name
             = section_name(section_list_get(sec, goods_index(pgood)));
 
-        if (!ruleset_load_names(&pgood->name, NULL, file, sec_name)) {
+            if (!ruleset_load_names(&pgood->name, NULL, file, names_hash,
+                                    sec_name)) {
           ruleset_error(NULL, LOG_ERROR,
                         "\"%s\": Cannot load goods names",
                         filename);
@@ -1389,6 +1414,7 @@ static bool load_game_names(struct section_file *file,
           break;
         }
       } goods_type_iterate_end;
+      names_cache_hash_destroy(names_hash);
     }
     section_list_destroy(sec);
   }
@@ -1410,6 +1436,7 @@ static bool load_game_names(struct section_file *file,
     if (ok) {
       int count_idx;
 
+      names_hash = names_cache_hash_new();
       game.control.num_counters = nval;
 
       for (count_idx = 0; count_idx < nval; count_idx++) {
@@ -1418,7 +1445,8 @@ static bool load_game_names(struct section_file *file,
         const char *sec_name
         = section_name(section_list_get(sec, count_idx));
 
-        if (!ruleset_load_names(&pcount->name, NULL, file, sec_name)) {
+        if (!ruleset_load_names(&pcount->name, NULL, file, names_hash,
+                                sec_name)) {
           ruleset_error(NULL, LOG_ERROR,
                         "\"%s\": Cannot load counters names",
                         filename);
@@ -1461,6 +1489,7 @@ static bool load_action_names(struct section_file *file,
 static bool load_tech_names(struct section_file *file,
                             struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   struct section_list *sec = NULL;
   /* Number of techs in the ruleset (means without A_NONE). */
   int num_techs = 0;
@@ -1523,11 +1552,13 @@ static bool load_tech_names(struct section_file *file,
     }
 
     if (ok) {
+      names_hash = names_cache_hash_new();
       tech_class_iterate(ptclass) {
         const char *sec_name
           = section_name(section_list_get(sec, tech_class_index(ptclass)));
 
-        if (!ruleset_load_names(&ptclass->name, NULL, file, sec_name)) {
+        if (!ruleset_load_names(&ptclass->name, NULL, file, names_hash,
+                                sec_name)) {
           ruleset_error(NULL, LOG_ERROR, "\"%s\": Cannot load tech class names",
                         filename);
           ok = FALSE;
@@ -1535,6 +1566,7 @@ static bool load_tech_names(struct section_file *file,
         }
       } tech_class_iterate_end;
     }
+    names_cache_hash_destroy(names_hash);
   }
 
   if (ok) {
@@ -1556,17 +1588,19 @@ static bool load_tech_names(struct section_file *file,
   }
 
   if (ok) {
+    names_hash = names_cache_hash_new();
     game.control.num_tech_types = num_techs + A_FIRST; /* includes A_NONE */
 
     i = 0;
     advance_iterate(adv) {
-      if (!ruleset_load_names(&adv->name, NULL, file,
+      if (!ruleset_load_names(&adv->name, NULL, file, names_hash,
                               section_name(section_list_get(sec, i)))) {
         ok = FALSE;
         break;
       }
       i++;
     } advance_iterate_end;
+    names_cache_hash_destroy(names_hash);
   }
   section_list_destroy(sec);
 
@@ -1790,6 +1824,7 @@ restart:
 static bool load_unit_names(struct section_file *file,
                             struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   struct section_list *sec = NULL;
   int nval = 0;
   int i;
@@ -1892,17 +1927,19 @@ static bool load_unit_names(struct section_file *file,
   }
 
   if (ok) {
+    names_hash = names_cache_hash_new();
     game.control.num_unit_classes = nval;
 
     unit_class_iterate(punitclass) {
       const int pci = uclass_index(punitclass);
 
-      if (!ruleset_load_names(&punitclass->name, NULL, file,
+      if (!ruleset_load_names(&punitclass->name, NULL, file, names_hash,
                               section_name(section_list_get(sec, pci)))) {
         ok = FALSE;
         break;
       }
     } unit_class_iterate_end;
+    names_cache_hash_destroy(names_hash);
   }
   section_list_destroy(sec);
   sec = NULL;
@@ -1926,16 +1963,18 @@ static bool load_unit_names(struct section_file *file,
   }
 
   if (ok) {
+    names_hash = names_cache_hash_new();
     game.control.num_unit_types = nval;
 
     unit_type_iterate(punittype) {
       const int utypei = utype_index(punittype);
-      if (!ruleset_load_names(&punittype->name, NULL, file,
+      if (!ruleset_load_names(&punittype->name, NULL, file, names_hash,
                               section_name(section_list_get(sec, utypei)))) {
         ok = FALSE;
         break;
       }
     } unit_type_iterate_end;
+    names_cache_hash_destroy(names_hash);
   }
   section_list_destroy(sec);
 
@@ -2547,6 +2586,7 @@ static bool load_ruleset_units(struct section_file *file,
 static bool load_building_names(struct section_file *file,
                                 struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   struct section_list *sec;
   int i, nval = 0;
   const char *filename = secfile_name(file);
@@ -2577,16 +2617,19 @@ static bool load_building_names(struct section_file *file,
   }
 
   if (ok) {
+    names_hash = names_cache_hash_new();
     game.control.num_impr_types = nval;
 
     for (i = 0; i < nval; i++) {
       struct impr_type *b = improvement_by_number(i);
 
-      if (!ruleset_load_names(&b->name, NULL, file, section_name(section_list_get(sec, i)))) {
+      if (!ruleset_load_names(&b->name, NULL, file, names_hash,
+                              section_name(section_list_get(sec, i)))) {
         ok = FALSE;
         break;
       }
     }
+    names_cache_hash_destroy(names_hash);
   }
 
   /* User building flag names */
@@ -2746,6 +2789,7 @@ static bool load_ruleset_buildings(struct section_file *file,
 static bool load_terrain_names(struct section_file *file,
                                struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   int nval = 0;
   struct section_list *sec = NULL;
   const char *flag;
@@ -2845,6 +2889,7 @@ static bool load_terrain_names(struct section_file *file,
   }
 
   if (ok) {
+    names_hash = names_cache_hash_new();
     game.control.terrain_count = nval;
 
     /* avoid re-reading files */
@@ -2857,13 +2902,15 @@ static bool load_terrain_names(struct section_file *file,
       const int terri = terrain_index(pterrain);
       const char *sec_name = section_name(section_list_get(sec, terri));
 
-      if (!ruleset_load_names(&pterrain->name, NULL, file, sec_name)) {
+      if (!ruleset_load_names(&pterrain->name, NULL, file, names_hash,
+                              sec_name)) {
         ok = FALSE;
         break;
       }
 
       section_strlcpy(&terrain_sections[terri * MAX_SECTION_LABEL], sec_name);
     } terrain_type_iterate_end;
+    names_cache_hash_destroy(names_hash);
   }
 
   section_list_destroy(sec);
@@ -2885,6 +2932,7 @@ static bool load_terrain_names(struct section_file *file,
   if (ok) {
     int idx;
 
+    names_hash = names_cache_hash_new();
     game.control.num_extra_types = nval;
 
     if (extra_sections) {
@@ -2897,13 +2945,15 @@ static bool load_terrain_names(struct section_file *file,
         const char *sec_name = section_name(section_list_get(sec, idx));
         struct extra_type *pextra = extra_by_number(idx);
 
-        if (!ruleset_load_names(&pextra->name, NULL, file, sec_name)) {
+        if (!ruleset_load_names(&pextra->name, NULL, file, names_hash,
+                                sec_name)) {
           ok = FALSE;
           break;
         }
         section_strlcpy(&extra_sections[idx * MAX_SECTION_LABEL], sec_name);
       }
     }
+    names_cache_hash_destroy(names_hash);
   }
 
   section_list_destroy(sec);
@@ -4218,6 +4268,7 @@ static bool load_ruleset_terrain(struct section_file *file,
 static bool load_government_names(struct section_file *file,
                                   struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   int nval = 0;
   struct section_list *sec;
   const char *filename = secfile_name(file);
@@ -4242,6 +4293,7 @@ static bool load_government_names(struct section_file *file,
   }
 
   if (ok) {
+    names_hash = names_cache_hash_new();
     governments_alloc(nval);
 
     /* Government names are needed early so that get_government_by_name will
@@ -4250,11 +4302,13 @@ static bool load_government_names(struct section_file *file,
       const char *sec_name =
         section_name(section_list_get(sec, government_index(gov)));
 
-      if (!ruleset_load_names(&gov->name, NULL, file, sec_name)) {
+        if (!ruleset_load_names(&gov->name, NULL, file,names_hash,
+                                 sec_name)) {
         ok = FALSE;
         break;
       }
     } governments_iterate_end;
+    names_cache_hash_destroy(names_hash);
   }
 
   section_list_destroy(sec);
@@ -4274,11 +4328,13 @@ static bool load_government_names(struct section_file *file,
     }
 
     if (ok) {
+      names_hash = names_cache_hash_new();
       multipliers_iterate(pmul) {
         const char *sec_name =
           section_name(section_list_get(sec, multiplier_index(pmul)));
 
-        if (!ruleset_load_names(&pmul->name, NULL, file, sec_name)) {
+        if (!ruleset_load_names(&pmul->name, NULL, file, names_hash,
+                                   sec_name)) {
           ruleset_error(NULL, LOG_ERROR,
                         "\"%s\": Cannot load multiplier names",
                         filename);
@@ -4286,6 +4342,7 @@ static bool load_government_names(struct section_file *file,
           break;
         }
       } multipliers_iterate_end;
+      names_cache_hash_destroy(names_hash);
     }
   }
 
@@ -4550,6 +4607,7 @@ static const char *check_leader_names(struct nation_type *pnation)
 static bool load_nation_names(struct section_file *file,
                               struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   struct section_list *sec;
   int j;
   bool ok = TRUE;
@@ -4575,6 +4633,7 @@ static bool load_nation_names(struct section_file *file,
   } else {
     game.control.nation_count = section_list_size(sec);
     nations_alloc(game.control.nation_count);
+    names_hash = names_cache_hash_new();
 
     nations_iterate(pl) {
       const int i = nation_index(pl);
@@ -4584,7 +4643,6 @@ static bool load_nation_names(struct section_file *file,
       const char *noun_plural = secfile_lookup_str(file,
                                                    "%s.plural", sec_name);
 
-
       if (domain == NULL) {
         domain = "freeciv-nations";
       }
@@ -4601,7 +4659,8 @@ static bool load_nation_names(struct section_file *file,
         break;
       }
 
-      if (!ruleset_load_names(&pl->adjective, pl->translation_domain, file, sec_name)) {
+      if (!ruleset_load_names(&pl->adjective, pl->translation_domain, file, names_hash,
+                              sec_name)) {
         ok = FALSE;
         break;
       }
@@ -4647,6 +4706,7 @@ static bool load_nation_names(struct section_file *file,
         break;
       }
     } nations_iterate_end;
+    names_cache_hash_destroy(names_hash);
   }
 
   section_list_destroy(sec);
@@ -5589,6 +5649,7 @@ static bool load_ruleset_nations(struct section_file *file,
 static bool load_style_names(struct section_file *file,
                              struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   bool ok = TRUE;
   struct section_list *sec;
   const char *filename = secfile_name(file);
@@ -5606,6 +5667,7 @@ static bool load_style_names(struct section_file *file,
                   "No available nation styles in this ruleset!");
     ok = FALSE;
   } else {
+    names_hash = names_cache_hash_new();
     game.control.num_styles = section_list_size(sec);
 
     styles_alloc(game.control.num_styles);
@@ -5614,8 +5676,9 @@ static bool load_style_names(struct section_file *file,
       const int i = style_index(ps);
       const char *sec_name = section_name(section_list_get(sec, i));
 
-      ruleset_load_names(&ps->name, NULL, file, sec_name);
+      ruleset_load_names(&ps->name, NULL, file, names_hash,sec_name);
     } styles_iterate_end;
+    names_cache_hash_destroy(names_hash);
   }
 
   section_list_destroy(sec);
@@ -5624,16 +5687,20 @@ static bool load_style_names(struct section_file *file,
     /* The citystyle sections: */
     int i = 0;
 
+    names_hash = names_cache_hash_new();
+
     sec = secfile_sections_by_name_prefix(file, CITYSTYLE_SECTION_PREFIX);
     if (NULL != sec) {
       city_styles_alloc(section_list_size(sec));
       section_list_iterate(sec, style) {
-        if (!ruleset_load_names(&city_styles[i].name, NULL, file, section_name(style))) {
+        if (!ruleset_load_names(&city_styles[i].name, NULL, file, names_hash,
+                                section_name(style))) {
           ok = FALSE;
           break;
         }
         i++;
       } section_list_iterate_end;
+      names_cache_hash_destroy(names_hash);
 
       section_list_destroy(sec);
     } else {
@@ -5829,6 +5896,7 @@ static bool load_muuk_as_action_auto(struct section_file *file,
 static bool load_ruleset_cities(struct section_file *file,
                                 struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   const char *filename = secfile_name(file);
   const char *item;
   struct section_list *sec;
@@ -5854,6 +5922,8 @@ static bool load_ruleset_cities(struct section_file *file,
     int i = 0;
     const char *tag;
 
+    names_hash = names_cache_hash_new();
+
     game.control.num_specialist_types = section_list_size(sec);
 
     section_list_iterate(sec, psection) {
@@ -5861,7 +5931,8 @@ static bool load_ruleset_cities(struct section_file *file,
       struct requirement_vector *reqs;
       const char *sec_name = section_name(psection);
 
-      if (!ruleset_load_names(&s->name, NULL, file, sec_name)) {
+      if (!ruleset_load_names(&s->name, NULL, file, names_hash,
+                              sec_name)) {
         ok = FALSE;
         break;
       }
@@ -5897,6 +5968,8 @@ static bool load_ruleset_cities(struct section_file *file,
       }
       i++;
     } section_list_iterate_end;
+
+    names_cache_hash_destroy(names_hash);
   }
 
   if (ok && DEFAULT_SPECIALIST == -1) {
@@ -6483,6 +6556,7 @@ static bool lookup_bv_actions(struct section_file *file,
 static bool load_ruleset_game(struct section_file *file, bool act,
                               struct rscompat_info *compat)
 {
+  struct names_cache_hash *names_hash;
   const char *sval, **svec;
   const char *filename;
   int *food_ini;
@@ -7190,6 +7264,7 @@ static bool load_ruleset_game(struct section_file *file, bool act,
   }
 
   if (ok) {
+    names_hash = names_cache_hash_new();
     disaster_type_iterate(pdis) {
       int id = disaster_index(pdis);
       int j;
@@ -7197,7 +7272,8 @@ static bool load_ruleset_game(struct section_file *file, bool act,
       struct requirement_vector *reqs;
       const char *sec_name = section_name(section_list_get(sec, id));
 
-      if (!ruleset_load_names(&pdis->name, NULL, file, sec_name)) {
+      if (!ruleset_load_names(&pdis->name, NULL, file, names_hash,
+                              sec_name)) {
         ruleset_error(NULL, LOG_ERROR,
                       "\"%s\": Cannot load disaster names",
                       filename);
@@ -7241,6 +7317,7 @@ static bool load_ruleset_game(struct section_file *file, bool act,
         break;
       }
     } disaster_type_iterate_end;
+    names_cache_hash_destroy(names_hash);
     section_list_destroy(sec);
   }
 
@@ -7475,6 +7552,8 @@ static bool load_ruleset_game(struct section_file *file, bool act,
       int num = section_list_size(sec);
       int curr;
 
+      names_hash = names_cache_hash_new();
+
       for (curr = 0; curr < num; curr++) {
 
         struct counter *pcount = counter_by_id(curr);
@@ -7493,7 +7572,8 @@ static bool load_ruleset_game(struct section_file *file, bool act,
           break;
         }
 
-        if (!ruleset_load_names(&pcount->name, NULL, file, sec_name)) {
+        if (!ruleset_load_names(&pcount->name, NULL, file, names_hash,
+                                 sec_name)) {
           ruleset_error(NULL, LOG_ERROR,
                         "\"%s\": Cannot load counter names",
                         filename);
@@ -7519,6 +7599,7 @@ static bool load_ruleset_game(struct section_file *file, bool act,
                                                  sec_name);
         attach_city_counter(pcount);
       }
+      names_cache_hash_destroy(names_hash);
     }
   }
 
-- 
2.39.2

