whisperity added inline comments.
================ Comment at: clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp:177 + const std::size_t StartIndex) { + const std::size_t NumParams = FD->getNumParams(); + assert(StartIndex < NumParams && "out of bounds for start"); ---------------- aaron.ballman wrote: > whisperity wrote: > > aaron.ballman wrote: > > > Some interesting test cases to consider: varargs functions and K&R C > > > functions > > > K&R C functions > > > > Call me too young, but I had to look up what a "K&R C function" is, and I > > am absolutely baffled how this unholy construct is still supported! > > **But:** thanks to Clang supporting it properly in the AST, the checker > > works out of the box! > > > > Given > > > > ``` > > int foo(a, b) > > int a; > > int b; > > { > > return a + b; > > } > > ``` > > > > We get the following output: > > > > ``` > > /tmp/knr.c:2:3: warning: 2 adjacent parameters of 'foo' of similar type > > ('int') are easily swapped by mistake [bugprone-easily-swappable-parameters] > > int a; > > ^ > > /tmp/knr.c:2:7: note: the first parameter in the range is 'a' > > int a; > > ^ > > /tmp/knr.c:3:7: note: the last parameter in the range is 'b' > > int b; > > ^ > > ``` > > > > (even the locations are consistent!) > > > > Should I add a test case for this? We could use a specifically C test case > > either way eventually... > > > > ----- > > > > > varargs functions > > > > This is a bit of terminology, but something tells me you meant the > > //variadic function// here, right? As opposed to type parameter packs. > > > > Given > > > > ``` > > int sum(int ints...) { > > return 0; > > } > > ``` > > > > the AST looks something like this: > > > > ``` > > `-FunctionDecl 0x56372e29e258 <variadic.cpp:1:1, line:3:1> line:1:5 sum > > 'int (int, ...)' > > |-ParmVarDecl 0x56372e29e188 <col:9, col:13> col:13 ints 'int' > > ``` > > > > Should we diagnose this? And if so, how? The variadic part is not > > represented (at least not at first glance?) in the AST. Understanding the > > insides of such a function would require either overapproximatic stuff and > > doing a looot of extra handling, or becoming flow sensitive... and we'd > > still need to understand all the `va_` standard functions' semantics either > > way. > > Call me too young, but I had to look up what a "K&R C function" is, and I > > am absolutely baffled how this unholy construct is still supported! > > Ah, to be innocent again, how I miss those days. :-D > > > Should I add a test case for this? We could use a specifically C test case > > either way eventually... > > I think it'd be a useful case, but the one I was specifically more concerned > with is: > ``` > // N.B.: this is C-specific and does not apply to C++. > void f(); > > int main(void) { > f(1, 2, 3.4, "this is why we can't have nice things"); > } > ``` > where the function has no prototype and so is treated as a varargs call. > > > This is a bit of terminology, but something tells me you meant the variadic > > function here, right? As opposed to type parameter packs. > > Yes, sorry for being unclear, I am talking about variadic functions. > > > Should we diagnose this? And if so, how? The variadic part is not > > represented (at least not at first glance?) in the AST. Understanding the > > insides of such a function would require either overapproximatic stuff and > > doing a looot of extra handling, or becoming flow sensitive... and we'd > > still need to understand all the va_ standard functions' semantics either > > way. > > Well, that's what I'm wondering, really. The arguments are certainly easy to > swap because the type system can't help the user to identify swaps without > further information (like format specifier strings). However, the checking > code would be... highly unpleasant, I suspect. My intuition is to say that we > don't support functions without prototypes at all (we just silently ignore > them) and that we only check the typed parameters in a variadic function > declaration (e.g., we'll diagnose `void foo(int i, int j, ...);` because of > the sequential `int` parameters, but we won't diagnose `void foo(int i, > ...);` even if call sites look like `foo(1, 2);`). WDYT? It is definitely highly unpleasant, at one point I remember just glancing into the logic behind `printf()` related warnings in Sema and it was... odd, to say the least. That is how the checker works right now. It will not diagnose `int f() {}` because from the looks of the function definition, it is a 0-parameter function. Same with the variadic `...`, it is a single parameter function (which has a rather //weird// parameter type). But yes, I think I'll make this explicit in the documentation (and FWIW, the research paper, too). > However, the checking code would be... highly unpleasant, I suspect. Incredibly, because this check purposefully targets the function definition nodes only, like a cruise missile. For that logic, we would need to gather call sites for variadic functions and start doing some more magic with it. FYI, templates are also handled in a way that only what is clear from definitions (and not instantiations... I think we can kind of call a particular "overload" call to a variadic like a template instantiation, just for the sake of the argument here) only: ``` template <typename T, typename U> void f(T t1, T t2, U u) {} // [t1, t2] same type, WARN. void g() { f(1, 2, 3); }} // NO-WARN: Even though "U" in this call is also "int", this is not fixed "easily enough" in the definition, so to prevent more false positives, we shut up. template <> void f(int i, int j, int k) {} // [i, k] WARN. ``` Anything that is only known through template instantiations is context-sensitive (the subsequent patch about typedef extends the tests and diagnostics about this), and thus, we forego trying to find the way. ``` template <typename T> struct vector { using value_type = T; }; template <typename T> void f(T t, typename vector<T>::element_type u) {} // NO-WARN, dependent. template <> void f(int i, vector<int>::element_type i2) {} // WARN, can explicitly unfold the typedef and see "int == int". template <typename T> void g(typename vector<T>::element_type e1, typename vector<T>::element_type e2) {} // WARN, not dependent. ``` ================ Comment at: clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp:177 + const std::size_t StartIndex) { + const std::size_t NumParams = FD->getNumParams(); + assert(StartIndex < NumParams && "out of bounds for start"); ---------------- whisperity wrote: > aaron.ballman wrote: > > whisperity wrote: > > > aaron.ballman wrote: > > > > Some interesting test cases to consider: varargs functions and K&R C > > > > functions > > > > K&R C functions > > > > > > Call me too young, but I had to look up what a "K&R C function" is, and I > > > am absolutely baffled how this unholy construct is still supported! > > > **But:** thanks to Clang supporting it properly in the AST, the checker > > > works out of the box! > > > > > > Given > > > > > > ``` > > > int foo(a, b) > > > int a; > > > int b; > > > { > > > return a + b; > > > } > > > ``` > > > > > > We get the following output: > > > > > > ``` > > > /tmp/knr.c:2:3: warning: 2 adjacent parameters of 'foo' of similar type > > > ('int') are easily swapped by mistake > > > [bugprone-easily-swappable-parameters] > > > int a; > > > ^ > > > /tmp/knr.c:2:7: note: the first parameter in the range is 'a' > > > int a; > > > ^ > > > /tmp/knr.c:3:7: note: the last parameter in the range is 'b' > > > int b; > > > ^ > > > ``` > > > > > > (even the locations are consistent!) > > > > > > Should I add a test case for this? We could use a specifically C test > > > case either way eventually... > > > > > > ----- > > > > > > > varargs functions > > > > > > This is a bit of terminology, but something tells me you meant the > > > //variadic function// here, right? As opposed to type parameter packs. > > > > > > Given > > > > > > ``` > > > int sum(int ints...) { > > > return 0; > > > } > > > ``` > > > > > > the AST looks something like this: > > > > > > ``` > > > `-FunctionDecl 0x56372e29e258 <variadic.cpp:1:1, line:3:1> line:1:5 sum > > > 'int (int, ...)' > > > |-ParmVarDecl 0x56372e29e188 <col:9, col:13> col:13 ints 'int' > > > ``` > > > > > > Should we diagnose this? And if so, how? The variadic part is not > > > represented (at least not at first glance?) in the AST. Understanding the > > > insides of such a function would require either overapproximatic stuff > > > and doing a looot of extra handling, or becoming flow sensitive... and > > > we'd still need to understand all the `va_` standard functions' semantics > > > either way. > > > Call me too young, but I had to look up what a "K&R C function" is, and I > > > am absolutely baffled how this unholy construct is still supported! > > > > Ah, to be innocent again, how I miss those days. :-D > > > > > Should I add a test case for this? We could use a specifically C test > > > case either way eventually... > > > > I think it'd be a useful case, but the one I was specifically more > > concerned with is: > > ``` > > // N.B.: this is C-specific and does not apply to C++. > > void f(); > > > > int main(void) { > > f(1, 2, 3.4, "this is why we can't have nice things"); > > } > > ``` > > where the function has no prototype and so is treated as a varargs call. > > > > > This is a bit of terminology, but something tells me you meant the > > > variadic function here, right? As opposed to type parameter packs. > > > > Yes, sorry for being unclear, I am talking about variadic functions. > > > > > Should we diagnose this? And if so, how? The variadic part is not > > > represented (at least not at first glance?) in the AST. Understanding the > > > insides of such a function would require either overapproximatic stuff > > > and doing a looot of extra handling, or becoming flow sensitive... and > > > we'd still need to understand all the va_ standard functions' semantics > > > either way. > > > > Well, that's what I'm wondering, really. The arguments are certainly easy > > to swap because the type system can't help the user to identify swaps > > without further information (like format specifier strings). However, the > > checking code would be... highly unpleasant, I suspect. My intuition is to > > say that we don't support functions without prototypes at all (we just > > silently ignore them) and that we only check the typed parameters in a > > variadic function declaration (e.g., we'll diagnose `void foo(int i, int j, > > ...);` because of the sequential `int` parameters, but we won't diagnose > > `void foo(int i, ...);` even if call sites look like `foo(1, 2);`). WDYT? > It is definitely highly unpleasant, at one point I remember just glancing > into the logic behind `printf()` related warnings in Sema and it was... odd, > to say the least. > > That is how the checker works right now. It will not diagnose `int f() {}` > because from the looks of the function definition, it is a 0-parameter > function. Same with the variadic `...`, it is a single parameter function > (which has a rather //weird// parameter type). > > But yes, I think I'll make this explicit in the documentation (and FWIW, the > research paper, too). > > > However, the checking code would be... highly unpleasant, I suspect. > > Incredibly, because this check purposefully targets the function definition > nodes only, like a cruise missile. For that logic, we would need to gather > call sites for variadic functions and start doing some more magic with it. > > FYI, templates are also handled in a way that only what is clear from > definitions (and not instantiations... I think we can kind of call a > particular "overload" call to a variadic like a template instantiation, just > for the sake of the argument here) only: > > ``` > template <typename T, typename U> > void f(T t1, T t2, U u) {} // [t1, t2] same type, WARN. > > void g() { f(1, 2, 3); }} // NO-WARN: Even though "U" in this call is also > "int", this is not fixed "easily enough" in the definition, so to prevent > more false positives, we shut up. > > template <> > void f(int i, int j, int k) {} // [i, k] WARN. > ``` > > Anything that is only known through template instantiations is > context-sensitive (the subsequent patch about typedef extends the tests and > diagnostics about this), and thus, we forego trying to find the way. > > ``` > template <typename T> struct vector { using value_type = T; }; > > template <typename T> > void f(T t, typename vector<T>::element_type u) {} // NO-WARN, dependent. > > template <> > void f(int i, vector<int>::element_type i2) {} // WARN, can explicitly unfold > the typedef and see "int == int". > > template <typename T> > void g(typename vector<T>::element_type e1, typename vector<T>::element_type > e2) {} // WARN, not dependent. > ``` I definitely shall create and add a C file with test cases like this, marking them `// NO-WARN`, and explaining the reasoning. If for nothing else, maybe to know in the far future what can be improved upon. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D69560/new/ https://reviews.llvm.org/D69560 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits