Compute GeoCoord's UTM/MGRS/OSGR/OLC lazily instead of eagerly (#10996)

GeoCoord's constructor unconditionally computed all five coordinate
representations (DMS, UTM, MGRS, OSGR, OLC) via setCoords(), even
though most callers only ever read one. The UTM conversion alone pulls
in a chain of libm trig functions (atan, __kernel_tan, __ieee754_acos,
__ieee754_pow, __kernel_rem_pio2/__ieee754_rem_pio2) that then have to
be linked in regardless of whether anything is ever displayed.

The only screen consumer (UIRenderer.cpp's GPS coordinate display)
already dispatches on a single configured format and reads exactly one
representation per call - never more than one. Compute DMS eagerly
(cheap, most commonly needed) and defer UTM/MGRS/OSGR/OLC to first
access via their own getters, tracked with per-representation mutable
dirty flags, so a caller that never touches a given representation
never pulls in its conversion code or the trig functions it needs.

On stm32wl, GeoCoord's heavy constructor is reached only through
NMEAWPL.cpp's NMEA/CalTopo serial export (GPS-gated, so wio-e5 only),
which only ever reads the DMS getters - so this recovers 8,288 bytes
flash there (of the 10,172-byte ceiling if the whole feature were cut)
with the feature fully intact and zero behavior change on any
platform.

Updated test_geocoord_extreme_coords_no_oob (the existing regression
test for a historical out-of-bounds crash in the UTM/MGRS conversion
on extreme lat/lon) to explicitly call each representation's getter,
since it previously relied on the constructor eagerly triggering all
four conversions - which this change intentionally defers. Verified:
full native test suite passes (586/586 test cases, including this one
under ASan), and rak4631 (nRF52, screen-equipped, where the
UTM/MGRS/OSGR/OLC getters are actually reachable) builds successfully.


Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>

Signed-off-by: Andrew Yong <me@ndoo.sg>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
Andrew Yong
2026-07-13 20:04:24 -05:00
committed by GitHub
co-authored by GitHub Ben Meadors
parent 269b974e96
commit dd509a8ecf
3 changed files with 140 additions and 30 deletions
+9 -2
View File
@@ -214,7 +214,8 @@ static void test_cryptoKeyIsPublic_invalidKeyIsNotPublic()
// Pre-fix, latitude_i = INT32_MAX made latLongToUTM read latBands[36] on a 21-char string
// (stack-buffer-overflow at GeoCoord.cpp:128, an AddressSanitizer abort); extreme longitude produced
// a negative UTM zone feeding the MGRS letter tables. The fix clamps the zone/band/col/row indices.
// This exercises the fix under the coverage env's ASan.
// This exercises the fix under the coverage env's ASan. Each representation is computed lazily,
// so its getter must be called explicitly here to exercise its conversion path.
static void test_geocoord_extreme_coords_no_oob()
{
const int32_t vals[] = {INT32_MIN, INT32_MAX, INT32_MIN + 1, INT32_MAX - 1, 0, 1, -1, 900000000, -900000000, // +/-90 deg
@@ -223,7 +224,13 @@ static void test_geocoord_extreme_coords_no_oob()
const size_t n = sizeof(vals) / sizeof(vals[0]);
for (size_t i = 0; i < n; i++)
for (size_t j = 0; j < n; j++) {
GeoCoord g(vals[i], vals[j], 0); // ctor -> setCoords() -> UTM/MGRS/OSGR/OLC
GeoCoord g(vals[i], vals[j], 0); // ctor -> setCoords() -> DMS only
// Force UTM/MGRS/OSGR/OLC computation (each lazy on first getter access).
g.getUTMZone();
g.getMGRSZone();
g.getOSGRE100k();
char olcCode[OLC_CODE_LEN + 1];
g.getOLCCode(olcCode);
// Surviving every extreme pair (no ASan fault) means the index clamps hold.
TEST_ASSERT_EQUAL_INT32(vals[i], g.getLatitude());
}