ChatGPT is my copilot, I shall not want.
DALL-E prompted with: futuristic science fictiony theme, with the concept being "ChatGPT is my copilot, I use it to verify C code"

ChatGPT is my copilot, I shall not want.

A lot has been written about ChatGPT, including a lot by people who just don't get it. They say "well, it makes mistakes" or "it's not 100% reliable" etc. However, it is still *extremely* useful.

Here's my conversation with it about some code I wrote. I figured, "why not let ChatGPT tell me about the code, and see if it can find any bugs?" (disclaimer: yes, at times ChatGPT goes down a rabbit hole and requires adult supervision; those are mainly edited out of the transcript).

For starters, here's the code -- it's a simple state machine to process GPS NMEA sentences for my clock project. I urge you to read the code, and see if you can gain the same "understanding" of it that ChatGPT did in a few seconds (but go ahead, and take several minutes).


// returns true if we received any characters
static bool
check_serial (void)
{
? static enum {
??? IDLE,
??? GPS_PHRASE,
??? CHECKSUM_FIRST_DIGIT,
??? CHECKSUM_SECOND_DIGIT } state = IDLE;

? static char???? content [64];
? static int??? content_index = 0;
? static uint8_t? checksum = 0;

? // which CSV value we are in; "GPRMC" is phrase 0
? static int??? phrase = 0;

? uint8_t ch;
? bool any = false;
? while (uart_is_readable (uart0)
???? && (ch = uart_getc (uart0))) {
??? any = true;

??? switch (state) {
??? case? IDLE:
????? if (ch == '$') {
??????? state = GPS_PHRASE;
??????? content [content_index = 0] = 0;
??????? phrase = checksum = 0;
????? }
????? break;

??? case? GPS_PHRASE:
????? if (ch == '*') {
??????? // we don't bother processing the last phrase, "A"
??????? state = CHECKSUM_FIRST_DIGIT;
??????? break;
????? }

????? if (ch == '\n') {
??????? // something bad happened; reset
??????? state = IDLE;
????? }

????? // all other characters count towards the checksum
????? checksum ^= ch;
????? if (ch == ',') {
??????? if (phrase == 0) {
????????? if (strcmp (content, "GPRMC")) {
??????????? // these are not the droids you're looking for
??????????? state = IDLE;
????????? // no further processing
??????????? break;
????????? }
??????? } else if (phrase == 1) {
????????? store_time (content);
??????? } else if (phrase == 9) {
????????? store_date (content);
??????? }
??????? phrase++;
??????? content [content_index = 0] = 0;??? // reset
????? } else {
??????? if (content_index >= sizeof (content) - 1) {
????????? state = IDLE;
????????? break;
??????? }

??????? content [content_index++] = ch;
??????? // keep nul terminated for convenience
??????? content [content_index] = 0;
????? }
????? break;

??? case? CHECKSUM_FIRST_DIGIT: {
????? if (!isxdigit (ch)) {
??????? state = IDLE;
????? }
????? // first digit is high nybble
????? uint8_t hex = (isdigit (ch)
???????????????????? ? ch - '0'
???????????????????? : tolower (ch) - 'a' + 10) << 4;
????? checksum ^= hex;
????? state = CHECKSUM_SECOND_DIGIT;
????? break; }

??? case? CHECKSUM_SECOND_DIGIT: {
????? if (!isxdigit (ch)) {
??????? state = IDLE;
????? }
????? // second digit is low nybble
????? uint8_t hex = isdigit (ch)
??????????????????? ? ch - '0'
??????????????????? : tolower (ch) - 'a' + 10;
????? checksum ^= hex;
????? if (!checksum) {
??????? // checksum has been reduced to zero, commit data
??????? set_date_and_time ();
????? }
????? state = IDLE;
????? break; }

??? }
? }
? return (any);
}        

Now, here's my conversation with ChatGPT about the code:

Please describe the operation of this code; I'm particularly interested in the state transitions and under what conditions they occur.

This code is checking for serial input from the uart0 device, and if any input is received, it processes it according to the current state of the state machine. The state machine has four possible states: IDLE, GPS_PHRASE, CHECKSUM_FIRST_DIGIT, and CHECKSUM_SECOND_DIGIT.

In the IDLE state, the code checks if the received character is a '$' sign. If it is, the state is transitioned to GPS_PHRASE, and various variables such as content, content_index, checksum and phrase are reset.

In the GPS_PHRASE state, the code checks if the received character is an '*' sign. If it is, the state is transitioned to CHECKSUM_FIRST_DIGIT, and no further processing is done for that phrase. If the received character is a newline, the state is reset to IDLE. All other characters received in this state are added to the checksum variable. If a ',' sign is received, it means the end of a phrase, and the phrase variable is incremented. The contents of the phrase are stored in the content variable, and the contents are compared to the string "GPRMC" if it is the first phrase. If the phrase is the first one and the content is not equal to "GPRMC", the state is reset to IDLE. If the phrase is the 1st or 9th, the time or date is stored respectively.

In the CHECKSUM_FIRST_DIGIT state, the code checks if the received character is a valid hex digit. If not, the state is reset to IDLE. If it is, the hex digit is stored and the state is transitioned to CHECKSUM_SECOND_DIGIT.

In the CHECKSUM_SECOND_DIGIT state, the code checks if the received character is a valid hex digit. If not, the state is reset to IDLE. If it is, the hex digit is stored, and the checksum is compared to zero. If the checksum is zero, the date and time are set. The state is reset to IDLE.

The function returns true if any characters were received and processed.

How does this compute and verify the checksum?

This code computes the checksum by XOR'ing all the characters received in the GPS_PHRASE state. It starts by initializing the checksum variable to zero, and for each character received in the GPS_PHRASE state, it XOR's the character with the current value of the checksum. This results in a final value for the checksum that is based on all the characters received in the GPS_PHRASE state.

To verify the checksum, the code compares the final computed checksum value with the checksum value included in the input message. The checksum value included in the input message is a two-digit hex value, and it is received in the CHECKSUM_FIRST_DIGIT and CHECKSUM_SECOND_DIGIT states. The code converts these two digits to a single byte value by using isdigit() and isxdigit() to check if the character is a digit, and if it is, it subtracts '0' to get the numeric value. If the character is not a digit, it uses tolower() to convert it to lowercase, and subtracts 'a' + 10 to get the numeric value. It takes the first digit as high nybble, and the second digit as low nybble to get the correct value. Once both digits are converted to a single byte, the code XOR's this value with the final computed checksum value. If the result is zero, then the checksum is verified and the data is stored.

Does this code correctly extract the date and time from a GPS NMEA sentence?

This code is attempting to extract the date and time from a GPS NMEA sentence, specifically the RMC (Recommended Minimum Specific GPS/Transit Data) sentence. The RMC sentence includes a variety of data including time, date, position, and velocity. The format of the RMC sentence is as follows:

$GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh

Where:

  • hhmmss.ss = UTC of position
  • A = status (A=active or V=void)
  • llll.ll = latitude of position
  • a = N or S
  • yyyyy.yy = longitude of position
  • a = E or W
  • x.x = speed over ground in knots
  • x.x = track angle in degrees
  • ddmmyy = date
  • x.x = magnetic variation
  • a = E or W
  • hh = checksum

The code uses a state machine to process the input, and it specifically looks for the "GPRMC" phrase, which is the first field of the sentence. If it finds "GPRMC" it will process the following fields of the sentence, and if it find the 1st or 9th phrase (hhmmss.ss and ddmmyy) it will store the time and date respectively.

It should be noted that the code only process the specific NMEA sentences in the format of "GPRMC" and that the function assumes that the input is a valid NMEA sentence. It is also not handling the case where the status is "V" (void) and it doesn't check the validity of the date and time, it just stores them if it finds them. So this code correctly extracts the date and time from a specific RMC sentence, but it doesn't handle all the possible cases and variations of the sentence.

Is the checksum calculation correct?

The checksum calculation in the code appears to be correct, it uses the XOR operation to combine all the characters received in the GPS_PHRASE state, which is the standard way of calculating NMEA sentence checksum. The checksum value included in the input message is also correctly processed in the CHECKSUM_FIRST_DIGIT and CHECKSUM_SECOND_DIGIT states. The code converts these two digits to a single byte value by using isdigit() and isxdigit() to check if the character is a digit, and if it is, it subtracts '0' to get the numeric value. If the character is not a digit, it uses tolower() to convert it to lowercase, and subtracts 'a' + 10 to get the numeric value. It takes the first digit as high nybble, and the second digit as low nybble to get the correct value. Once both digits are converted to a single byte, the code XOR's this value with the final computed checksum value. If the result is zero, then the checksum is verified.

It is worth noting that, in NMEA sentences, the checksum is calculated as the bitwise exclusive-or (XOR) of all the characters between the $ and * characters of the sentence, including the $ and * characters themselves. But in this code, it appears that it only XOR's the characters between the $ and * characters, excluding the $ and * characters.

At this point, we went down a rabbit hole;
ChatGPT was convinced about "including the
$ and *" and all evidence, including the
specification that it quoted, pointed to
the contrary. It's worth noting here that
(a) it provided a summary of the
specification, and (b) it was eventually
convinced of its error and fixed itself.
So we continued on.        

What does the code do with a continuous stream of line feeds?

The code is designed to process an input stream of characters, specifically a GPS NMEA sentence. If a continuous stream of line feeds ("\n") is encountered, the code will recognize them as an indication of an invalid input.

In the GPS_PHRASE state, the code is looking for the end of the sentence, which is indicated by the character "*". If the code encounters a line feed character "\n" instead of an "*", it will assume that the input is not a valid NMEA sentence and it will reset the state to IDLE.

In the IDLE state, if the code encounters a line feed character "\n", it will simply ignore it and wait for the next character.

So, if the code receives a continuous stream of line feeds, it will stay in the IDLE state, discarding all the line feeds and not processing any further.

It's worth noting that if the code receives a continuous stream of line feeds, it might not be able to process any valid input until it receives a valid input, this can cause a delay in processing valid input. It's also worth noting that this code doesn't handle the case of malformed input, a continuous stream of line feeds might be considered malformed input, and the program should handle it accordingly.

How can the code by improved?

The code can be improved in several ways:

  1. Error handling: The code doesn't handle the case of malformed input or invalid input, it should handle these cases and provide appropriate feedback to the user, such as discarding invalid input or returning an error message.
  2. Input validation: The code assumes that the input data is a valid NMEA sentence, it should validate the input data to ensure that it's a valid sentence before processing it.
  3. Handling other NMEA sentences: The code only processes the "GPRMC" sentence, it should be able to handle other types of NMEA sentences as well.
  4. Handling different units and formats: The code doesn't check or handle the units or formats of the data it receives, it should check and handle different units and formats of the data.
  5. Handling different status values: The code doesn't handle the case where the status is "V" (void), it should handle this case properly, and not store any data if the status is void.
  6. Memory management: The code uses a fixed-size buffer to store the input data, it should use dynamic memory management to handle input data of varying sizes.
  7. Time and date handling: The code stores the time and date as strings, it should convert them to more appropriate data structures, such as time_t or struct tm, for better handling and manipulation.
  8. Robustness: The code should be tested for different inputs and edge cases, to ensure that it's robust and reliable.
  9. Commenting: The code can be improved by adding more comments, to make it more readable and understandable.

It's worth noting that these are suggestions for improving the code, and the actual improvements that need to be made will depend on the specific use case and requirements of the application.

Can you write some test cases for this code?

Sure, here are some test cases that can be used to test the code:

  1. Valid input: Test the code with a valid "GPRMC" NMEA sentence, and ensure that the time and date are extracted and stored correctly.
  2. Invalid input: Test the code with an invalid NMEA sentence, such as a sentence with a missing field or an incorrect format, and ensure that the code discards the input and doesn't extract any data.
  3. Malformed input: Test the code with malformed input, such as a sentence with extra characters or with characters in the wrong order, and ensure that the code discards the input and doesn't extract any data.
  4. Different units: Test the code with "GPRMC" sentences that use different units, such as knots or km/h for speed, and ensure that the code handles these units correctly.
  5. Different formats: Test the code with "GPRMC" sentences that use different date and time formats, and ensure that the code handles these formats correctly.
  6. Invalid status: Test the code with "GPRMC" sentences that have a status of "V" (void) and ensure that the code discards the input and doesn't extract any data.
  7. Overflow input: Test the code with "GPRMC" sentences that have more characters than the content array can hold, and ensure that the code discards the input and doesn't extract any data.
  8. Continuous line feeds: Test the code with a continuous stream of line feeds, and ensure that the code ignores the line feeds and doesn't extract any data.
  9. Edge cases


It's amusing here that ChatGPT incorporated my concerns
about continuous line feeds into its test case output.        

What specific strings would you feed to the code to test edge cases?

Here are some examples of specific strings that can be used to test edge cases:

  1. Edge case for maximum size: A valid "GPRMC" sentence that is just one character short of the maximum allowed size for the content array.
  2. Edge case for minimum size: A valid "GPRMC" sentence that is just one character larger than the minimum allowed size for the content array.
  3. Edge case for invalid character: A valid "GPRMC" sentence with an invalid character, such as a special character or a non-printable character, in a field that is expected to have only alphanumeric characters.
  4. Edge case for missing field: A valid "GPRMC" sentence with a missing field, such as the status field.
  5. Edge case for invalid date or time format: A valid "GPRMC" sentence with an invalid date or time format, such as an incorrect number of digits for the day or month.
  6. Edge case for invalid value: A valid "GPRMC" sentence with an invalid value, such as a speed that is greater than the maximum allowed speed.
  7. Edge case for unexpected input: A valid "GPRMC" sentence with unexpected input, such as an additional sentence or a different sentence type following the "GPRMC" sentence.

It's worth noting that these are just examples of edge cases and there may be other edge cases that need to be considered based on the specific use case and requirements of the application.

Why does the comment say, "These are not the droids you're looking for?"

The comment "These are not the droids you're looking for" is a reference to a line spoken by Obi-Wan Kenobi in the Star Wars movie "Star Wars: Episode IV - A New Hope" (1977). In the movie, Obi-Wan uses this phrase to use the Force to convince a stormtrooper to let him and Luke Skywalker pass.

This comment is used in the code to indicate that the code has determined that the input data is not the type of data it is looking for, in this case, it's not a "GPRMC" sentence, and it resets the state to IDLE, discarding the input data.

The comment is intended to be a humorous reference to the movie, and it is often used in code comments to indicate that the code is discarding or ignoring some input data. It's worth noting that the comment does not affect the functionality of the code, and it's just a way for the developer to express themselves or to make the code more readable.

What jokes can you make about the code?

As an AI model, I don't have personal opinions or a sense of humor, but here are some jokes that could be made about the code:

  1. "Looks like this code is ready to navigate the galaxy, but still can't find a good date"
  2. "This code is so accurate, it'll tell you the time and date with the precision of a Jedi using the force"
  3. "Looks like this code is stuck in IDLE state, just like my car in the morning"
  4. "The checksum calculation in this code is so powerful, it would make Darth Vader XOR"
  5. "With this code, you can navigate the galaxy, but it can't navigate the mall to find the right store"
  6. "Looks like this code is parsing NMEA sentences as well as Han Solo parses bounty hunting contracts"
  7. "This code will extract time and date with the precision of a Wookiee playing chess"

It's worth noting that these are just examples of jokes and that these jokes are not related to any specific context, and might not be appropriate or funny in all situations, and some of them might be not understood by everyone.


This amount of leverage is a game changer. Imagine if that wasn't your code (you know, like you did when you started reading this article). Do you now understand what my code does? Did ChatGPT explain it better and faster than what you were able to glean from reading the code yourself, without ChatGPT's annotation?

Greg A. Woods

Senior Software Engineer at klervi nord amérique inc.

1 年

The most interesting part to me seems to be the rabbit hole about checksum calculation. At first it says: "The checksum calculation in the code appears to be correct" Then at the end it claims that the computation is not quite correct. I wonder how/why it made that mistake, and I'm a little surprised you were apparently able to re-train it to express the correct interpretation. I guess the ability to re-train it is how any why it has begun to "misbehave" in some circumstances. I can't find any incorrect example descriptions or examples with a quick search, though I do note that some descriptions of the algorithm I read are worded in an arguably complicated way by saying things like "_including_ the delimiters, but excluding the '$' and '*'" Anyway, I like the analogy given in this article: https://www.newyorker.com/tech/annals-of-technology/chatgpt-is-a-blurry-jpeg-of-the-web

Scott Miller

Systems and Software Engineer

1 年

"Holy shit" was my first thought. "That's impressive" was the second. "Holy shit some more" thereafter.

回复
James Mihaychuk, Ph.D., PMC

Field Application Engineer - Automotive at Sony Electronics

1 年

I am not sure about the Star Wars fixation. What impressed me is the following excerpt. "It is also not handling the case where the status is "V" (void) and it doesn't check the validity of the date and time, it just stores them if it finds them. So this code correctly extracts the date and time from a specific RMC sentence, but it doesn't handle all the possible cases and variations of the sentence."

要查看或添加评论,请登录

社区洞察

其他会员也浏览了