-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Recent pgmspace.h broke backward compability for pgm_read_xxx() macros #4572
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Actually the cast needs to be like this:
|
@bperrybap do you have a code snippet which fails to compile with the current implementation? There's a PR now open which adds the casts, and I wanted to make sure we have a test for this. |
Not sure why a test is needed as the issue and the code to fix it is seems pretty obvious. Here is some sample test code that should compile without issues or warnings.
|
The purpose of having a test is to prevent future regressions, should someone need to modify pgm_read_X macros/functions again. About your test cases. These compile with the current code and do not produce errors or warnings: data8[0] = pgm_read_byte(data8_ptr); // uint8_t *
data8[1] = pgm_read_byte(&data8_ptr[1]); // uint8_t *
data8[2] = pgm_read_byte(data8_ptr + 2); // uint8_t * pointer math
data16[0] = pgm_read_word(data16_ptr); // uint16_t *
data16[1] = pgm_read_word(&data16_ptr[1]); // uint16_t *
data16[2] = pgm_read_word(data16_ptr + 2); // uint16_t * pointer math These do not compile, and i don't think they should. The reason that such code compiles for AVR is not strong enough justification to allow silent casts from integer to pointer type. data8[0] = pgm_read_byte(DATA_ADDR); // #define
data8[0] = pgm_read_byte(data_addr); // int
data16[0] = pgm_read_word(DATA_ADDR); // #define
data16[0] = pgm_read_word(data_addr); // int For example, the following is a clear usage error, and should not compile: const uint8_t pgm_array[] PROGMEM = {1, 2, 3, 4};
void setup() {
uint8_t data = pgm_read_byte(pgm_array[1]); // user forgot the address-of operator
} but with the proposed change, this would compile and crash. I don't think that applying |
The example code was very simplistic intended to test against the old esp8266 core, vs the latest, vs the PR. While it does demonstrate the real-world argument type issue, it should not be taken as an example of the actual code that compiled and worked on AVR and used to work on esp8266 but fails now fails to compile on the latest esp8266 core. The actual code that I have seen failing is part of larger projects that are doing much more complex operations but end up creating/using integer types that get passed to pgm_read_xxx() functions. The main issue that that integer types can be passed in on AVR and older esp8266 cores but fail to compile on the latest esp8266 core.
You can see the forced cast and there is no type checking. In terms of where would these constants come from other than progmem variables?.... But here is one that I think users might expect to work: (and it did in the past)
Yes they could fix it with a cast, but they didn't have to do that in the past. So the real issue is that integer types used to work but now they don't. Another thing related to pgmspace compatibility (but totally unrelated to this issue) is that pgmspace.h should really be down in a directory called avr. (other cores put it there for compatibility)
This breaks on the esp8266 core. |
I do understand the dilemma. It is a difficult question of what is more important. |
My opinion is that we should maintain compatibility in all reasonable use cases. Automatic casting of integer types to pointers is not reasonable, in my opinion. The issue you mention (nested pgm_read calls) seems to be already fixed in the library, by the way: Bodmer/TFT_eSPI#15. |
While I made the PR for this in the sake of backward compatibility, @igrr came with very valid arguments. |
I also agree with @igrr on this. It's a similar case as we already had with the min/max macros. We switched to the std functions, and issues rained due to other code not compiling anymore. On inspection, all those cases were due to bad habits, and they had to be fixed, which was a Good Thing. |
Yes the specific example code I posted was from one of Bodmer's libraries. I talked to him about the issue a few weeks ago. But this issue was actually in his older version of the library, TFT_ILI9341_ESP and it came up because a few folks were trying to run one of his Arduino sketches that used the older library, that was not ported to his new version of the graphics library (he has just recently done that as well). When I was younger, I did have a much more rigid approach to trying to force people to do the right thing, I've mellowed out over the decades, and while I do agree that supporting bad/sloppy programming habits is definitely not ideal, I do have much more sympathy to supporting them when it comes to trying maintain an existing programming interface behavior in order to support previously existing code. While I'm all for good type checking to try to catch unintentional coding errors, this particular case does seem a bit special and a bit less clear as to which way to go given it is emulating a proprietary API defined by an alternate tool set. As I mentioned earlier, one of the areas of compatibility already currently not supported in the esp8266 core is the location of the pgmspace.h header. It should be in a subdirectory called avr for maximum compatibility with existing older AVR Arduino code that included <avr/pgmspace.h> But some of the functional/compilation issues around progmem stuff is in the "official" AVR pgmspace.h code itself. Perhaps having some sort of conditional in the esp8266 pgmspace.h that could be enabled is a reasonable compromise here? I wouldn't be upset if after the discussion about this issue, the decision is made to not add the casts, and change the status to "won't fix" - knowing the implications of that decision. I'm just trying to encourage the discussion to make everyone aware that the recent changes do break some existing code, including code that used to work on previous versions of the esp8266 core. |
I think Ivan has stated a very compelling case with "Automatic casting of integer types to pointers is not reasonable". Novice or advanced programmers should not be doing this unless they know exactly what they are doing. |
I don't agree that automatic casting is necessarily unreasonable - in this particular case I think it is not so clear. IMO, the progmem stuff has always been very ugly and messy, but the entire point of having pgmspace.h in the esp8266 core is to emulate the functionality/behavior of the AVR proprietary progmem stuff, and the latest esp8266 core doesn't fully do that for some real-world use cases, while the older cores did. The primary issue is that it isn't possible to have type checking and full compatibility with the sloppy AVR pgmspace implementation. In some cases advanced programmers knew exactly what they were doing and the new esp8266 pgmspace code breaks their code that used to work on the esp8266. Also consider that pgm_read_byte() and pgm_read_word() are not functions. It could be decided that type checking is more important than full AVR pgmspace progmem emulation, to ensure better and more reliable code moving forward, and that is ok with me, but then it brings up what I mentioned earlier and that is the possibility of creating a backward compatibility define just like what the AVR did guys when they broke their original pgmspace behavior - perhaps even a define with the same define name the AVR guys used. |
#4619 now adds optional casts, gated by preprocessor macro. I'm okay with that solution. |
There were some updates to pgmspace.h in mid 2017 that changed the way pgm_read_byte() and pgm_read_word() worked and were implemented.
When the dust settled, the final implementation breaks some use cases as it is no longer fully backward compatible with the AVR version.
The AVR version did not require the address argument to be any particular type and neither did the esp version until these recent updates.
This issue can be traced back to the commit on April 17, 2017
0b67266#diff-2b6ff2e1583a8a247dc1070d846b5160
That is the first version of code where the addr argument must be a specific type.
Then after that update, future updates built on that and ended up with the macros pgm_read_byte() and pgm_read_word() simply calling functions which expected an argument type.
This can be seen in the june 5 commit:
c52b0da#diff-2b6ff2e1583a8a247dc1070d846b5160
with these two AVR compatibility macros:
While those macros solved a big concern in the discussion of having the needed macros to test for PROGMEM support at compile time, they break AVR compatibility by not casting the addr argument.
This means that any code that is not using the argument type expected by pgm_read_byte_inlined() or pgm_read_word_inlined() (which is a void *) will fail from a type mismatch.
There is quite a bit of code out there that was not using void * for this argument and some of that code was previously working on esp until these updates were done.
A simple solution is add a cast.
The text was updated successfully, but these errors were encountered: