-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Added bounds checking on pinMode #302
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -169,24 +169,51 @@ extern const uint8_t PROGMEM digital_pin_to_port_PGM[]; | |
// extern const uint8_t PROGMEM digital_pin_to_bit_PGM[]; | ||
extern const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[]; | ||
extern const uint8_t PROGMEM digital_pin_to_timer_PGM[]; | ||
extern const uint8_t digital_pin_count; | ||
|
||
#define NOT_A_PIN 0 | ||
#define NOT_A_PORT 0 | ||
|
||
#define NOT_AN_INTERRUPT -1 | ||
|
||
// Get the bit location within the hardware port of the given virtual pin. | ||
// This comes from the pins_*.c file for the active board configuration. | ||
// | ||
// These perform slightly better as macros compared to inline functions | ||
// | ||
#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) ) | ||
#define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) ) | ||
#define digitalPinToTimer(P) ( pgm_read_byte( digital_pin_to_timer_PGM + (P) ) ) | ||
// | ||
#define analogInPinToBit(P) (P) | ||
#define portOutputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_output_PGM + (P))) ) | ||
#define portInputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_input_PGM + (P))) ) | ||
#define portModeRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_mode_PGM + (P))) ) | ||
|
||
#define NOT_A_PIN 0 | ||
#define NOT_A_PORT 0 | ||
|
||
#define NOT_AN_INTERRUPT -1 | ||
// | ||
// bounds checking on these functions because they return a pointer to an address that will be modified. | ||
// An out of bounds write can result in stack corruption and fun to track-down errors. | ||
// | ||
static inline const uint8_t digitalPinToPort(const uint8_t P) { | ||
if (P > digital_pin_count) { | ||
BarryPSmith marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return NOT_A_PIN; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me whether NOT_A_PIN is an invalid port number, or whether it is a valid port number that is returned for an invalid pin. Can you add a comment to make that more clear? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you reviewing the latest commit? That should be a It's following the same convention already in place in Do you think that it is not obvious from the code what the situation is, that a comment is needed? |
||
} else { | ||
return pgm_read_byte( digital_pin_to_port_PGM + P ); | ||
} | ||
} | ||
static inline const uint8_t digitalPinToBitMask(const uint8_t P) { | ||
if (P > digital_pin_count) { | ||
return NOT_A_PIN; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly here, it is not clear whether There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you reviewing the latest commit? That should be a It's following the same convention already in place in Do you think that it is not obvious from the code what the situation is, that a comment is needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was: the semantics of the The (The term "PORT" seems to be overloaded in The Similarly, the These 3 functions are returning 3 conceptually separate things, and I got confused why all of them are returning a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please familiarise yourself with how these functions are currently used and implemented.They have no documentation of their API, so their existing use defines the API. wiring_digital.c and the various pins_arduino.h are good places to look. You will see that the new implementations are consistent with the existing API, as I explained above. You are requesting that the existing API change. That is out of scope of this pull request. Please create a separate pull request or issue. I do not feel that a comment explaining that a function conforms to the rest of the (undocumented, uncommented) API is necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's even more important to add comments to describe your assumptions when you are making changes on top of undocumented and uncommented API. I have reviewed the usage of As far as I can tell, If you choose to ignore my feedback and continue to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For
For
For
So, for The same logic applies to Unfortunately it appears that the writer of So:
Both of those options are out of scope for this PR and more than I'm prepared to do. As I say, please deal with the issue in a separate PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand the intended limited scope of this PR. But your changes to The problem that you found in The return type of The return type of Finally, the return type of case NOT_ON_TIMER:
default:
if (val < 128) {
digitalWrite(pin, LOW);
} else {
digitalWrite(pin, HIGH);
} If At this point, the simplest fix I can see that will implement the intended scope of this PR is to remove the checks for static inline uint8_t digitalPinToBitMask(const uint8_t P) {
return pgm_read_byte( digital_pin_to_bit_mask_PGM + P );
}
static inline uint8_t digitalPinToTimer(const uint8_t P) {
return pgm_read_byte( digital_pin_to_timer_PGM + P );
} I cannot see how returning the |
||
} else { | ||
return pgm_read_byte( digital_pin_to_bit_mask_PGM + P ); | ||
} | ||
} | ||
static inline const uint8_t digitalPinToTimer(const uint8_t P) { | ||
if (P > digital_pin_count) { | ||
return NOT_A_PIN; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you reviewing the latest commit? That should be a It's following the same convention already in place in Do you think that it is not obvious from the code what the situation is, that a comment is needed? |
||
} else { | ||
return pgm_read_byte( digital_pin_to_timer_PGM + P ); | ||
} | ||
} | ||
// these defines are to maintain backwards compatibility with #ifdef | ||
#define digitalPinToPort digitalPinToPort | ||
#define digitalPinToBitMask digitalPinToBitMask | ||
#define digitalPinToTimer digitalPinToTimer | ||
|
||
#ifdef ARDUINO_MAIN | ||
#define PA 1 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for using
static
andinline
at the same time?If a function is inlined, then there should be no symbol for it, so there should be no link time issue. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inline
is only a hint, the compiler can choose to ignore it and often does for large enough functions. If thestatic
was not there, I believe the linker will (may?) complain about duplicate definitions ofdigitalPinToPort()
. Not 100% sure, I'm not in front of a computer where I can check this. With link-time-optimization (--lto), I think any duplicate definitions will be optimized out.Interestingly enough, it is the first
const
keyword in that line that is unnecessary, sinceuint8_t
is returned by-value. The compiler is probably printing a warning, which will show up if warnings are turned on.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know
inline
is just a hint, but seems the story is complicated by the fact there were implementations before standardization:http://www.greenend.org.uk/rjk/tech/inline.html
After reading this, and after a recollection from some other discussion I had some time ago, to have a single stand-alone copy, according to the standard, there needs to be 1 and only 1 declaration of the inline function in a .c file, while the definition can be in .h.
Given the usage here, i guess
static inline
is the safest and simplest choice for this code.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, C++ handles the way to generate the
inline
standalone copy differently than C (definition/declaration rules are different).I think I saw an explanation in some youtube video, but I can't recall which one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally I tried it with just
inline
, but I got linker errors complaining about unresolved symbols. I did some searching and it seemedstatic inline
was the most similar to what one expects a macro to do (which is what this function is replacing) - i.e. only work if the definition is included in the current compilation unit.(Side note: I'm very surprised about the existing comment in the code that macros are faster than inline functions. Perhaps -Os is making the compiler ignore the
inline
hint? Seems strange, in my projects the compiler inlines half of my larger functions straight intmain
.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually,
inline
is not so much a hint to inline, but it changes the linkage of the symbol, telling the linker to not complain if multiple definitions are found (just drop all but one and use that one). This is relevant since when an inline function is not not inlined every time, a definition is also emitted into the .o file. Effectively,inline
should be used whenever defining a function in a header file. Defining it in the header allows inlining, while theinline
keyword prevents linker errors. Also note that with link-time-optimization, also functions in .cpp files can get inlined.When using
static
, I believe that means that each .o file will get is own private definition of the function, which automatically will not conflict and not produce multiple defintion errors (so addinginline
is effectively pointless here). This also means that if the function is not inlined in multiple .o files, each of these will get their own private, distinct definition of the function and you will end up with multiple definitions of the same function.So, just
inline
rather thanstatic inline
is better, since then any duplicate definitions gets merged.That seems weird, maybe we should investigate and solve those?
Well, macros are necessarily inlined, while inline functions might not be if the compiler thinks this is better. If you really want to ensure inlining, you can use the
always_inline
attribute (but often it is better to let the compiler decide).This seems all wrong. A declaration has no function body and is usually in a .h file (since it can be included from multiple .c/.cpp files without conflict), while a definition has a function body and is typically in the .c/.cpp file to prevent multiple definition errors (unless the function is
inline
, then it can appear in a .h file and be included from multiple .c/.cpp files).I was not aware of any such differences (but that might be me). There is a difference in calling convention for all functions (C++ mangles function names to include the argument types, to allow argument overloading), maybe that is what you recall?
Indeed, that is a pointless
const
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is information that indicates that's not how gcc behaves under std=gnu11 (what the arduino build environment runs). See https://blahg.josefsipek.net/?p=529 . Look at the row 'inline' column 'Emit (C99)' in the table 5 paragraphs down. Do I misread the blog post, or is it wrong?
What are the parameters you would like to investigate? What is not solved? What behaviour do you believe exists that you would like to be different?
Here's how I understand it:
inline
and the compiler decides not to inline the function, then it generates a linker error. I find it strange that the compiler would choose not to inline this function (bounds check + array lookup seems like even if optimising for size it's less code space than a function call). Perhaps there's something with LTO also happening here that the linker error occurs?inline static
and the compiler can inline it everywhere, then no symbol is emitted. If it cannot inline it everywhere, then a symbol is emitted. I hope that LTO takes care to avoid multiple symbols, but I'm not sure - is this what you would like to investigate?inline
in the header with aextern inline
in a .c file, then there must be a symbol emitted. Does LTO have the option to not include that symbol? I don't know that either.If my understanding is confused, I would appreciate clarification. There does seem to be some conflicting information around given the various inline behaviours.
A final option would be to get rid of
inline
entirely, and make it a normal function and hope that LTO inlines it wherever its used.Also: If you remove the
static
, does it compile without problems for you? Seems a simple test; if it does then I'll try figure out why it won't compile for me.Here is a gnu.org article that suggests the definition should be in the header file, and the "declaration" in the c file (there were a few other sources similar, but I didn't write them down): https://www.gnu.org/software/gnulib/manual/html_node/extern-inline.html
I believe this might be what you are advocating, but if so are you certain that it still gives the linker the freedom to strip the symbol if it is inlined everywhere? Also, do you think the added complexity of having to have an
extern inline
declaration somewhere (I assume wiring.c would make the most sense) is worth it to save code space in the edge case where the function isn't inlined and used in multiple translation units?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are the exact linker errors if I remove
static
:I'm not running the Arduino IDE, but use the Arduino libraries. As such, perhaps I see this error because I don't run GCC exactly the same (but I have looked at what the Arduino IDE does and tried to match it). The options I pass to GCC are:
It still emits the same errors if I change it to std=gnu11 (to match the Arduino IDE).