c++regexgpsnmea

Cannot find why my regex match is kinda smashed while reading my GPS buffer


I am reading NMEA-0183 message with C++ 11.

After a code revamp, I see some kind of buffer smashing and I cannot see why. Help is welcome.

Here are the logs I get:

GPS: probably data
GPS: $GPGLL,4851.87109,N,00226.20521,E,092741.00,A,A*6D
GPS: match is: 1 | 5 matches
GPS: match #0: 17a30a0 | buffer: 5f452ee0
GPS: match #0: B2z
GPS: match #1: 5f452ed0 | buffer: 5f452ee0
GPS: match #1: 
GPS: match #2: 5f452ed0 | buffer: 5f452ee0
GPS: match #2: N
GPS: match #3: 5f452ed0 | buffer: 5f452ee0
GPS: match #3: 00226.20521
GPS: match #4: 5f452ed0 | buffer: 5f452ee0
GPS: match #4: E
GPS: match is: latitude:0.000000 | longitude: 2.436754
GPS: end of processing

Where I expect something like this (note this is the GPGLL message which works the same way as the one up above):

GPS: probably data
GPS: $GPRMC,164548.00,A,4851.85459,N,00226.22224,E,0.721,,060923,,,A*7F
GPS: match is: 1 | 5 matches
GPS: match #0: GPRMC,164548.00,A,4851.85459,N,00226.22224,E
GPS: match #1: 4851.85459
GPS: match #2: N
GPS: match #3: 00226.22224
GPS: match #4: E
GPS: match is: latitude:48.864243 | longitude: 2.437037
GPS: end of processing

And the code:

#include <iostream>
#include <regex>

/// @brief Returns the 01131.324 (Longitude 11 deg 31.324') coordinate as a
/// float
/// @param sReading The string of the coordinate
/// @return the float version
float latitudeDeegresToFloat(char const *sReading) {
  char sDegrees[2 + 1] = {sReading[0], sReading[1], 0};
  float degrees = (float)strtod(sDegrees, NULL);
  float minutes = (float)strtod(sReading + 2, NULL);
  const float latitude = degrees + minutes / 60.0;

  return latitude;
}

/// @brief Returns the 11131.324 longitude (111 deg 31.324') coordinate as a
/// float
/// @param sReading The string of the coordinate
/// @return The float version
float longitudeDeegresToFloat(char const *sReading) {
  char sDegrees[3 + 1] = {sReading[0], sReading[1], sReading[2], 0};
  float degrees = (float)strtod(sDegrees, NULL);
  float minutes = (float)strtod(sReading + 3, NULL);
  const float longitude = degrees + minutes / 60.0;

  return longitude;
}

int main() {
  float latitude, longitude;

  printf("GPS: probably data\n");

  /// Regex string match
  std::smatch match;

  char buffer[256 * 4] = "$GPGLL,4851.87109,N,00226.20521,E,092741.00,A,A*6D";

  printf("GPS: %s\n", buffer);
  bool found = false;

// == Is is a GPGLL message Geographic Acquisition
#define COMMAND "$GPGGA"
  if (strncmp(buffer, COMMAND, sizeof COMMAND - 1) == 0) {

    //$GPGGA,123519,4807.038,N,01131.324,E,
    std::string str(buffer + 1); // Skip leading $
    std::regex rgx("GPGGA,[^,]*,([0-9\\.+-]+),(N|S),([0-9\\.+-]*),(E|W)\\.?");
    found = std::regex_search(str, match, rgx);
  }
  // == Is is a GPGLL message Geographic Latitude Longitude
#define COMMAND "$GPGLL"
  else if (strncmp(buffer, COMMAND, sizeof COMMAND - 1) == 0) {

    std::string str(buffer + 1); // Skip leading $
    std::regex rgx("GPGLL,([0-9\\.+-]+),(N|S),([0-9\\.+-]*),(E|W)\\.?");
    found = std::regex_search(str, match, rgx);
  }

// == Is is a GPRMC message (lat, long)
#define GPRMC "$GPRMC"
  else if (strncmp(buffer, GPRMC, sizeof GPRMC - 1) == 0) {
    std::string str(buffer + 1); // Skip leading $
    std::regex rgx("GPRMC,[^,]*,A,([0-9\\.+-]+),(N|S),([0-9\\.+-]*),(W|E)\\.?");
    found = std::regex_search(str, match, rgx);

  } else {
    printf("GPS: not a message to process but '%s'\n", buffer);
    return 3;
  }

  printf("GPS: match is: %d | %d matches\n", found, match.size());
  if (!found)
    return 1;

  int i = 0;

  for (auto m : match) {
    printf("GPS: match #%d: %x | buffer: %x\n", i, m.str().c_str(), buffer);
    printf("GPS: match #%d: %s\n", i, m.str().c_str());

    switch (i) {
    // Latitude
    case 1:
      latitude = latitudeDeegresToFloat(m.str().c_str());
      break;
    // Latitude direction
    case 2:
      if (m.str().compare("S") == 0)
        latitude = -latitude;
      break;
    // Longitude
    case 3:
      longitude = longitudeDeegresToFloat(m.str().c_str());
      break;
    case 4:
      if (m.str().compare("W") == 0)
        longitude = -longitude;
      break;
    }

    i++;
  }
  printf("GPS: match is: latitude:%f | longitude: %f\n", latitude, longitude);

  // == End of GPS processing
EndOfGpsProcessing:
  printf("GPS: end of processing\n");

  if (latitude == 0 && longitude == 0) {
    printf("GPS: won't publish Null Island\n");
    return 2;
  }

  return 0;
}

Here is a replit to work with: https://replit.com/@Stephanede/GPS-regex


Solution

  • The documentation of std::match_results (which is what your std::smatch actually is) explains what is going on:

    Because std::match_results holds std::sub_matches, each of which is a pair of iterators into the original character sequence that was matched, it's undefined behavior to examine std::match_results if the original character sequence was destroyed or iterators to it were invalidated for other reasons.

    The str you pass into std::regex_search must outlive match, which is not the case.

    You can actually do away with str entirely and just call regex_search(buffer, match, rx). Alternatively, create a std::string_view that wraps buffer at the top level or promote str to the top level of your function.