44d8050c280e78571f46ea34b1ebffedca9758ce
braney
Wed Sep 2 11:20:27 2026 -0700
vcfClick: print only the columns a tabular INFO value actually has, refs #38172
printTabularData() dropped the return value of chopByChar and then looped to
the column count taken from the header row. chopByChar fills only the slots
it uses, so a value carrying fewer pipe separated fields than its Description
declares took the loop past the end of what was filled. looksTabular()
requires just one of an element's values to match the header, so the rest of
a multi-value element can be shorter. Keep the count and print an empty cell
past it.
Both functions also sized a stack array off VCF text with char copy[len+1],
one from a record's INFO value and one from the header Description. Use the
heap for those.
Raised in the v503 Preview II code review.
diff --git src/hg/hgc/vcfClick.c src/hg/hgc/vcfClick.c
index 20db79efdd0..34cdf55bb9a 100644
--- src/hg/hgc/vcfClick.c
+++ src/hg/hgc/vcfClick.c
@@ -103,69 +103,74 @@
static int printTabularHeaderRow(struct trackDb *tdb, const struct vcfInfoDef *def)
/* Parse the column header parts out of def->description and print as table header row;
* call this only when looksTabular returns TRUE.
* Returns the number of columns in the header */
{
regmatch_t substrArr[PATH_LEN];
if (regexMatchSubstr(def->description, COL_DESC_REGEX, substrArr, ArraySize(substrArr)))
{
puts("
");
// Make a copy of the part of def->description that matches the regex,
// then chop by '|' and print out header column tags:
int matchSize = substrArr[0].rm_eo - substrArr[0].rm_so;
- char copy[matchSize+1];
- safencpy(copy, sizeof(copy), def->description + substrArr[0].rm_so, matchSize);
+ // The description comes from the VCF header, so it can be any length; keep it off the stack.
+ char *copy = cloneStringZ(def->description + substrArr[0].rm_so, matchSize);
// Turn '_' into ' ' so description words can wrap inside headers, saving some space
subChar(copy, '_', ' ');
char *words[PATH_LEN];
int descColCount = chopByChar(copy, '|', words, ArraySize(words));
int i;
for (i = 0; i < descColCount; i++)
printf("| %s | ", hubEncode(tdb, words[i]));
puts("
");
+ freeMem(copy);
return descColCount;
}
else
errAbort("printTabularHeaderRow: code bug, if looksTabular returns true then "
"regex should work here");
return -1;
}
static void printTabularData(struct trackDb *tdb, struct vcfInfoElement *el, int headerCount)
/* Print a row for each value in el, separating columns by '|'. */
{
int j;
for (j = 0; j < el->count; j++)
{
puts("");
char *val = el->values[j].datString;
if (!isEmpty(val))
{
- int len = strlen(val);
- char copy[len+1];
- safencpy(copy, sizeof(copy), val, len);
+ // The value comes from the VCF, so it can be any length; keep it off the stack.
+ char *copy = cloneString(val);
char *words[PATH_LEN];
- chopByChar(copy, '|', words, ArraySize(words));
+ int wordCount = chopByChar(copy, '|', words, ArraySize(words));
int k;
// printTabularHeaderRow strips off (but still prints!) a trailing '|'
// because of the regex, so enforce that here too so the rows after
- // the header don't get all out of whack
+ // the header don't get all out of whack. A value can carry fewer
+ // fields than the header describes, and chopByChar fills only the
+ // slots it used, so print an empty cell rather than reading past
+ // wordCount into uninitialized stack.
for (k = 0; k < headerCount; k++)
- printf("| %s | ", hubEncode(tdb, words[k]));
+ printf("%s | ",
+ k < wordCount ? hubEncode(tdb, words[k]) : "");
+ freeMem(copy);
}
puts("
");
}
}
static void printInfoDatum(struct trackDb *tdb, const union vcfDatum datum,
const enum vcfInfoType type)
/* Print one INFO value. Same as vcfPrintDatum, except that the string forms are escaped for
* a hub track, where the VCF file was written by a stranger. */
{
if (type == vcfInfoString || type == vcfInfoFlag)
{
char *val = hubEncode(tdb, datum.datString);
if (startsWith("http", datum.datString))