e2d86e5b1db3e603f503f58db38009fa31840dd5
braney
  Wed Sep 9 12:37:09 2026 -0700
hgTracks: don't read past the end of exonFrames on a transcript's last exon

The exon mouseover works out the codon phase at each end of an exon.  The end
phase is the frame of the next exon along the transcript.  On the last exon of
a forward-strand transcript there is no next exon, and the index has reached
the number of exons, so the read was one element past the end of the array.
The reverse-strand branch already guarded the same case at its own end of the
transcript.

Nothing the reader sees changes.  makeExonFrameText prints an end phase only
when the exon is not the last one, so the value read here was always thrown
away.  Measured on two builds from this tree, patched and not: 591 codon-phase
tooltips at chr12:459,900-462,400 are byte for byte the same, while valgrind
reports the invalid read in the unpatched build and none in this one.

refs #38309

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git src/hg/hgTracks/simpleTracks.c src/hg/hgTracks/simpleTracks.c
index 8babeeef356..3ee4d114db6 100644
--- src/hg/hgTracks/simpleTracks.c
+++ src/hg/hgTracks/simpleTracks.c
@@ -3162,31 +3162,35 @@
                 existingText = lf->name;
 
             // construct a string that tells the user about the codon frame situation of this exon
             // char *frameText = "";
             // for coding exons, determine the start and end phase of the exon and an English text describing both:
             // if transcript is on + strand, the start phase is the exonFrame value, and the end phase is the next exonFrame (3' on DNA) value
             // if transcript is on - strand, the start phase is the previous (=3' on DNA) exonFrame and the end phase is the exonFrame
             int startPhase = -1;
             int endPhase = -1;
             char phaseText[EXONTEXTLEN];
             phaseText[0] = 0;
             if ((gp != NULL) && gp->exonFrames && isExon)
                 {
                 startPhase = gp->exonFrames[exonIx-1];
                 if (!revStrand)
+                    {
+                    // the last exon has no next exon, so it has no end phase
+                    if (exonIx < gp->exonCount)
                         endPhase = gp->exonFrames[exonIx];
+                    }
                 else 
                     if (exonIx>1)
                         endPhase = gp->exonFrames[exonIx-2];
                 // construct a string that tells the user about the codon frame situation of this exon
                 makeExonFrameText(exonIntronNumber, numExons, startPhase, endPhase, phaseText);
                 }
 
 	    if (w > 0) // draw exon or intron if width is greater than 0
 		{
                 // draw mapBoxes for the codons if we are zoomed in far enough
                 if (isExon && lf->codons && zoomedToCdsColorLevel)
                     {
                     struct simpleFeature *codon;
                     struct dyString *codonDy = dyStringNew(0);
                     int codonS, codonE;