38bab7f62d250db395c83bd4b6b0d6c08799ef59
markd
  Sat Aug 15 20:01:54 2026 -0700
Make maf UI species arrays dynamic to fix crash on large mafs. refs #38039

The Codon Translation dropdown in wigMafCfgUi built its option list in a
fixed nodeNames[512] stack array, so a maf with more than 511 species wrote
past the end of the frame and crashed hgTrackUi.  Size it from slCount of the
species list instead.

Three more fixed arrays in the same path silently dropped data rather than
crashing: wigMafGetSpecies chopped speciesOrder into species[2000] and
speciesGroups into a 1000 entry array, and wigMafSpeciesTable chopped
speciesDefaultOff into words[2000].  All now count with chopLineLen and
allocate.

Verified by driving wigMafCfgUi directly: 700 species segfaulted before and
renders now, and 3000 species previously lost everything past 2000.  hgTrackUi
output for hg38 multiz470way is unchanged.

diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index 03f819249e5..19e5c9a0ddb 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -8127,80 +8127,83 @@
     char *suffix = option + strlen(option);
     int suffixSize = optionSize - strlen(option);
     safef(suffix,suffixSize,".%s",species);
     }
 return cartUsualBooleanClosestToHome(cart,tdb, parentLevel, species,defaultState);
 }
 
 char **wigMafGetSpecies(struct cart *cart, struct trackDb *tdb, char *prefix, char *db,
                         struct wigMafSpecies **list, int *groupCt)
 {
 int speciesCt = 0;
 char *speciesGroup   = trackDbSetting(tdb, SPECIES_GROUP_VAR);
 char *speciesUseFile = trackDbSetting(tdb, SPECIES_USE_FILE);
 char *speciesOrder   = trackDbSetting(tdb, SPECIES_ORDER_VAR);
 #define MAX_SP_SIZE 2000
-#define MAX_GROUPS 1000
 char sGroup[MAX_SP_SIZE];
 //Ochar *groups[20];
 struct wigMafSpecies *wmSpecies, *wmSpeciesList = NULL;
 int group;
 int i;
-char *species[MAX_SP_SIZE];
+char **species = NULL;
 char option[MAX_SP_SIZE];
 
 *list = NULL;
 *groupCt = 0;
 
 /* determine species and groups for pairwise -- create checkboxes */
 if (speciesOrder == NULL && speciesGroup == NULL && speciesUseFile == NULL)
     {
     if (isCustomTrack(tdb->track))
 	return NULL;
     errAbort("Track %s missing required trackDb setting: speciesOrder, speciesGroups, or speciesUseFile", tdb->track);
     }
 
-char **groups = needMem(MAX_GROUPS * sizeof (char *));
+int groupsSize = (speciesGroup != NULL ? chopLineLen(speciesGroup) : 1);
+char **groups = needMem(groupsSize * sizeof (char *));
 *groupCt = 1;
 if (speciesGroup)
-    *groupCt = chopByWhite(speciesGroup, groups, MAX_GROUPS);
+    *groupCt = chopByWhite(speciesGroup, groups, groupsSize);
 
 if (speciesUseFile)
     {
     if ((speciesGroup != NULL) || (speciesOrder != NULL))
 	errAbort("Can't specify speciesUseFile and speciesGroup or speciesOrder");
     speciesOrder = cartGetOrderFromFile(db, cart, speciesUseFile);  // Not sure why this is in cart
     }                                                          // not tdb based so no ClosestToHome
 
 for (group = 0; group < *groupCt; group++)
     {
     if (*groupCt != 1 || !speciesOrder)
         {
         safef(sGroup, sizeof sGroup, "%s%s",
                                 SPECIES_GROUP_PREFIX, groups[group]);
         speciesOrder = trackDbRequiredSetting(tdb, sGroup);
         }
-    speciesCt = chopLine(speciesOrder, species);
+    speciesCt = chopLineLen(speciesOrder);
+    AllocArray(species, speciesCt);
+    chopByWhite(speciesOrder, species, speciesCt);
     for (i = 0; i < speciesCt; i++)
         {
         AllocVar(wmSpecies);
         wmSpecies->name = cloneString(species[i]);
         safecpy(option,sizeof option,prefix);
 	wmSpecies->on = isSpeciesOn(cart, tdb, wmSpecies->name, option, sizeof option, TRUE);
         wmSpecies->group = group;
         slAddHead(&wmSpeciesList, wmSpecies);
         }
+    freez(&species);
     }
 slReverse(&wmSpeciesList);
 *list = wmSpeciesList;
 
 return groups;
 }
 
 
 struct wigMafSpecies * wigMafSpeciesTable(struct cart *cart,
                                           struct trackDb *tdb, char *name, char *db)
 {
 int groupCt;
 #define MAX_SP_SIZE 2000
 char option[MAX_SP_SIZE];
 int group, prevGroup;
@@ -8211,52 +8214,54 @@
 
 struct wigMafSpecies *wmSpeciesList;
 char **groups = wigMafGetSpecies(cart, tdb, name, db, &wmSpeciesList, &groupCt);
 struct wigMafSpecies *wmSpecies = wmSpeciesList;
 struct slName *speciesList = NULL;
 
 for(; wmSpecies; wmSpecies = wmSpecies->next)
     {
     struct slName *newName = slNameNew(wmSpecies->name);
     slAddHead(&speciesList, newName);
     }
 slReverse(&speciesList);
 
 int numberPerRow;
 boolean lineBreakJustPrinted;
-char *words[MAX_SP_SIZE];
+char **words = NULL;
 int defaultOffSpeciesCnt = 0;
 
 if (cartOptionalString(cart, "ajax") == NULL)
     jsIncludeFile("utils.js",NULL);
 //jsInit();
 puts("\n<P><B>Species selection:</B>&nbsp;");
 
 cgiContinueHiddenVar("g");
 char id[256];
 PLUS_BUTTON( "id", "plus_pw","cb_maf_","_maf_")
 MINUS_BUTTON("id","minus_pw","cb_maf_","_maf_")
 
 char prefix[512];
 safef(prefix, sizeof prefix, "%s.", name);
 char *defaultOffSpecies = trackDbSetting(tdb, "speciesDefaultOff");
 struct hash *offHash = NULL;
 if (defaultOffSpecies)
     {
     offHash = newHash(5);
     DEFAULT_BUTTON( "id", "default_pw","cb_maf_","_maf_")
-    int wordCt = chopLine(defaultOffSpecies, words);
+    int wordCt = chopLineLen(defaultOffSpecies);
+    AllocArray(words, wordCt);
+    chopByWhite(defaultOffSpecies, words, wordCt);
     defaultOffSpeciesCnt = wordCt;
 
     /* build hash of species that should be off */
     int ii;
     for(ii=0; ii < wordCt; ii++)
         hashAdd(offHash, words[ii], NULL);
     }
 
 if (groupCt == 1)
     puts("\n<TABLE><TR>");
 group = -1;
 lineBreakJustPrinted = FALSE;
 for (wmSpecies = wmSpeciesList, i = 0, j = 0; wmSpecies != NULL;
 		    wmSpecies = wmSpecies->next, i++)
     {
@@ -8315,30 +8320,31 @@
         label = remapName;
     else
         {
         label = hOrganism(wmSpecies->name);
         if (label == NULL)
                 label = wmSpecies->name;
         if (lowerFirstChar)
             *label = tolower(*label);
         }
     printf("%s<BR>", label);
     puts("</TD>");
     lineBreakJustPrinted = FALSE;
     j++;
     }
 puts("</TR></TABLE><BR>\n");
+freez(&words);
 return wmSpeciesList;
 }
 
 void wigMafCfgUi(struct cart *cart, struct trackDb *tdb,char *name, char *title, boolean boxed, char *db)
 /* UI for maf/wiggle track
  * NOTE: calls wigCfgUi */
 {
 int i;
 char option[MAX_SP_SIZE];
 boolean parentLevel = isNameAtParentLevel(tdb,name);
 
 boxed = cfgBeginBoxAndTitle(tdb, boxed, title);
 
 char *defaultCodonSpecies = trackDbSetting(tdb, SPECIES_CODON_DEFAULT);
 char *framesTable = trackDbSetting(tdb, "frames");
@@ -8401,54 +8407,57 @@
     else
         puts("Display unaligned bases with spanning chain as 'o's<BR>");
     }
 
 safef(option, sizeof option, "%s.%s", name, MAF_SHOW_SNP);
 if (snpTable)
     {
     printf("<BR><B>Codon Changes:</B><BR>");
     cgiMakeCheckBox(option, cartOrTdbBoolean(cart, tdb, MAF_SHOW_SNP,FALSE));
     puts("Display synonymous and non-synonymous changes in coding exons.<BR>");
     }
 
 safef(option, sizeof option, "%s.%s", name, "codons");
 if (framesTable)
     {
-    char *nodeNames[512];
+    int nodeCount = slCount(wmSpeciesList) + 1;   /* +1 for reference db */
+    char **nodeNames;
     char buffer[128];
 
+    AllocArray(nodeNames, nodeCount);
     printf("<BR><B>Codon Translation:</B><BR>");
     printf("Default species to establish reading frame: ");
     nodeNames[0] = db;
     for (wmSpecies = wmSpeciesList, i = 1; wmSpecies != NULL;
 			wmSpecies = wmSpecies->next, i++)
 	{
         nodeNames[i] = wmSpecies->name;
         }
-    cgiMakeDropList(SPECIES_CODON_DEFAULT, nodeNames, i,     // tdb independent var
+    cgiMakeDropList(SPECIES_CODON_DEFAULT, nodeNames, nodeCount,     // tdb independent var
                     cartUsualString(cart, SPECIES_CODON_DEFAULT, defaultCodonSpecies));
     puts("<br>");
     char *cartVal = cartUsualStringClosestToHome(cart, tdb, parentLevel, "codons","codonDefault");
     safef(buffer, sizeof(buffer), "%s.codons",name);
     cgiMakeRadioButton(buffer,"codonNone",     sameWord(cartVal,"codonNone"));
     printf("No codon translation<BR>");
     cgiMakeRadioButton(buffer,"codonDefault",  sameWord(cartVal,"codonDefault"));
     printf("Use default species reading frames for translation<BR>");
     cgiMakeRadioButton(buffer,"codonFrameNone",sameWord(cartVal,"codonFrameNone"));
     printf("Use reading frames for species if available, otherwise no translation<BR>");
     cgiMakeRadioButton(buffer,"codonFrameDef", sameWord(cartVal,"codonFrameDef"));
     printf("Use reading frames for species if available, otherwise use default species<BR>");
+    freeMem(nodeNames);
     }
 else
     {
     /* Codon highlighting does not apply to wigMafProt type */
     if (!strstr(tdb->type, "wigMafProt"))
         {
         puts("<P><B>Codon highlighting:</B><BR>" );
 
 #ifdef GENE_FRAMING
 
         safef(option, sizeof(option), "%s.%s", name, MAF_FRAME_VAR);
         char *currentCodonMode = cartCgiUsualString(cart, option, MAF_FRAME_GENE);
 
         /* Disable codon highlighting */
         cgiMakeRadioButton(option, MAF_FRAME_NONE,