whisperity updated this revision to Diff 315384.
whisperity added a comment.
Herald added a subscriber: shchenz.
**Ignore one-way implicit conversions**
One-way implicit conversions introduce too much noise, and except in very odd
circumstances, do not provide a meaningful result.
The stalwart example
void f(const Base& bp, const Derived& dp)
contains just one conversion, `dp -> bp`, which, if called in a swapped way,
will 99.9...% of the cases will result in a compile error (*)
Base b;
Derived d;
f(d, b);
Thus, there is no reason to model this, at all.
(*): Unless an `operator Derived()` exists in `Base`.
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D75041/new/
https://reviews.llvm.org/D75041
Files:
clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp
clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h
clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst
clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-cvr-on.cpp
clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.c
clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.cpp
clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp
clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c
Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c
+++ clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.c
@@ -32,3 +32,6 @@
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: 2 adjacent parameters for 'equals2' of similar type ('S') are
// CHECK-MESSAGES: :[[@LINE-2]]:15: note: the first parameter in this range is 'l'
// CHECK-MESSAGES: :[[@LINE-3]]:20: note: the last parameter in this range is 'r'
+
+void ptrs(int *i, long *l) {}
+// NO-WARN: Mixing fundamentals' pointers is diagnosed by compiler warnings.
Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp
+++ clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-minlen3.cpp
@@ -203,3 +203,20 @@
// CHECK-MESSAGES: :[[@LINE-6]]:25: note: after resolving type aliases, type of parameter 'Background' is 'OldSchoolTermColour'
// CHECK-MESSAGES: :[[@LINE-6]]:25: note: after resolving type aliases, type of parameter 'Foreground' is 'OldSchoolTermColour'
// CHECK-MESSAGES: :[[@LINE-6]]:25: note: after resolving type aliases, type of parameter 'Border' is 'OldSchoolTermColour'
+
+// NO-WARN: Implicit conversions should not warn if the check option is turned off.
+
+void integral1(signed char sc, int si) {}
+
+struct FromInt {
+ FromInt(int);
+};
+void userConv1(int i, FromInt fi) {}
+
+struct Base {};
+struct Derived1 : Base {};
+struct Derived2 : Base {};
+void upcasting(Base *bp, Derived1 *d1p, Derived2 *d2p) {}
+void upcasting_ref(const Base &br, const Derived1 &d1r, const Derived2 &d2r) {}
+
+// END of NO-WARN.
Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.cpp
@@ -0,0 +1,308 @@
+// RUN: %check_clang_tidy %s \
+// RUN: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type %t \
+// RUN: -config='{CheckOptions: [ \
+// RUN: {key: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.ImplicitConversion, value: 1} \
+// RUN: ]}' --
+
+void compare(int a, int b, int c) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: 3 adjacent parameters for 'compare' of similar type ('int') are easily swapped by mistake [experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type]
+// CHECK-MESSAGES: :[[@LINE-2]]:18: note: the first parameter in this range is 'a'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'c'
+
+void b(bool b1, bool b2, int i) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 3 adjacent parameters for 'b' of convertible types may be easily swapped by mistake [experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type]
+// CHECK-MESSAGES: :[[@LINE-2]]:13: note: the first parameter in this range is 'b1'
+// CHECK-MESSAGES: :[[@LINE-3]]:30: note: the last parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'bool' and 'int' can suffer implicit conversions between one another{{$}}
+
+void integral1(signed char sc, int si) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral1' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:28: note: the first parameter in this range is 'sc'
+// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'si'
+// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'signed char' and 'int' can suffer implicit conversions between one another{{$}}
+
+void integral2(long l, int i) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral2' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'l'
+// CHECK-MESSAGES: :[[@LINE-3]]:28: note: the last parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'long' and 'int' can suffer implicit conversions between one another{{$}}
+
+void integral3(int i, long l) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral3' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:20: note: the first parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-3]]:28: note: the last parameter in this range is 'l'
+// CHECK-MESSAGES: :[[@LINE-4]]:23: note: 'int' and 'long' can suffer implicit conversions between one another{{$}}
+
+void integral4(char c, int i, long l) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 3 adjacent parameters for 'integral4' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'c'
+// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'l'
+// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'char' and 'int' can suffer implicit conversions between one another{{$}}
+// CHECK-MESSAGES: :[[@LINE-5]]:31: note: 'char' and 'long' can suffer implicit conversions between one another{{$}}
+
+void floating1(float f, double d) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'floating1' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'f'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'd'
+// CHECK-MESSAGES: :[[@LINE-4]]:25: note: 'float' and 'double' can suffer implicit conversions between one another{{$}}
+
+typedef double D;
+void floating2(float f, D d) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'floating2' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'f'
+// CHECK-MESSAGES: :[[@LINE-3]]:27: note: the last parameter in this range is 'd'
+// CHECK-MESSAGES: :[[@LINE-4]]:16: note: after resolving type aliases, type of parameter 'f' is 'float'
+// CHECK-MESSAGES: :[[@LINE-5]]:25: note: after resolving type aliases, type of parameter 'd' is 'double'
+// CHECK-MESSAGES: :[[@LINE-6]]:25: note: 'float' and 'double' can suffer implicit conversions between one another{{$}}
+
+void floatToInt(float f, int i) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent parameters for 'floatToInt' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'f'
+// CHECK-MESSAGES: :[[@LINE-3]]:30: note: the last parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'float' and 'int' can suffer implicit conversions between one another{{$}}
+
+enum En { A,
+ B };
+
+// NO-WARN: In C++, unscoped enums can be converted to scalars, but not the
+// other way around.
+
+void unscopedEnumToIntegral(En e, int i) {} // NO-WARN
+
+void unscopedEnumToIntegral2(En e, unsigned long long ull) {} // NO-WARN
+
+void unscopedEnum3(char c, En e, char c2) {} // NO-WARN
+
+void unscopedEnumToFloating(En e, long double ld) {} // NO-WARN
+
+void unscopedEnumToIntOrFloat(En e, int i, float f) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:37: warning: 2 adjacent parameters for 'unscopedEnumToIntOrFloat' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:41: note: the first parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-3]]:50: note: the last parameter in this range is 'f'
+// CHECK-MESSAGES: :[[@LINE-4]]:44: note: 'int' and 'float' can suffer implicit conversions between one another{{$}}
+
+enum class SEn { C,
+ D };
+void scopedEnumToIntegral(SEn e, int i) {}
+// NO-WARN: Scoped enumerations mustn't be promoted.
+
+struct FromInt {
+ FromInt(int);
+};
+
+// NO-WARN: User-defined conversions in just one direction.
+
+void userConv1(int i, FromInt fi) {}
+
+void userConv1c(int i, const FromInt cfi) {}
+// NO-WARN: CVRMixPossible is set to 0, so an 'int' and a 'const "int"' does not mix.
+
+void userConv1cr(int i, const FromInt &cfir) {}
+
+void userConv2(unsigned long long ull, FromInt fi) {}
+
+struct ToInt {
+ operator int() const;
+};
+
+void userConv3(int i, ToInt ti) {}
+
+void userConv4(double d, FromInt fi) {}
+
+void userConv4cr(double d, const FromInt &cfir) {}
+
+struct FromDouble {
+ FromDouble(double);
+};
+
+void userConv5(En e, FromDouble fd) {}
+
+struct Ambiguous {
+ Ambiguous(int);
+ Ambiguous(long);
+};
+
+void userConv6(En e, Ambiguous a) {}
+
+struct ToExplicitInt {
+ explicit operator int() const;
+};
+struct FromExplicitInt {
+ explicit FromExplicitInt(int);
+};
+
+void nonConverting1(int i, FromExplicitInt fei) {}
+
+void nonConverting2(int i, ToExplicitInt tei) {}
+
+struct Both {
+ Both(int);
+ operator int() const;
+};
+
+typedef int I;
+typedef double D;
+
+void both1(int i, Both b) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 2 adjacent parameters for 'both1' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:16: note: the first parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-3]]:24: note: the last parameter in this range is 'b'
+// CHECK-MESSAGES: :[[@LINE-4]]:19: note: 'int' and 'Both' can suffer implicit conversions between one another{{$}}
+
+void both2(double d, Both b) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 2 adjacent parameters for 'both2' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:19: note: the first parameter in this range is 'd'
+// CHECK-MESSAGES: :[[@LINE-3]]:27: note: the last parameter in this range is 'b'
+// CHECK-MESSAGES: :[[@LINE-4]]:22: note: 'double' and 'Both' can suffer implicit conversions between one another: 'double' -> 'int' -> 'Both' and 'Both' -> 'int' -> 'double'
+
+void both2typedef(D d, Both b) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters for 'both2typedef' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'd'
+// CHECK-MESSAGES: :[[@LINE-3]]:29: note: the last parameter in this range is 'b'
+// CHECK-MESSAGES: :[[@LINE-4]]:19: note: after resolving type aliases, type of parameter 'd' is 'double'
+// CHECK-MESSAGES: :[[@LINE-5]]:24: note: after resolving type aliases, type of parameter 'b' is 'Both'
+// CHECK-MESSAGES: :[[@LINE-6]]:24: note: 'double' and 'Both' can suffer implicit conversions between one another: 'double' -> 'int' -> 'Both' and 'Both' -> 'int' -> 'double'
+
+struct BoxedInt {
+ BoxedInt();
+ BoxedInt(const BoxedInt &);
+ BoxedInt(BoxedInt &&);
+ BoxedInt(const Both &B);
+};
+
+void both3(int i, BoxedInt biv) {}
+// NO-WARN: Two converting constructor calls (int->both->BoxedInt) is not
+// possible implicitly.
+
+struct IntAndDouble {
+ IntAndDouble(const int);
+ IntAndDouble(const double);
+
+ operator int() const;
+ operator double() const;
+};
+
+void twoTypes1(int i, IntAndDouble iad) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'twoTypes1' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:20: note: the first parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'iad'
+// CHECK-MESSAGES: :[[@LINE-4]]:23: note: 'int' and 'IntAndDouble' can suffer implicit conversions between one another{{$}}
+
+void twoTypes2(double d, IntAndDouble iad) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'twoTypes2' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'd'
+// CHECK-MESSAGES: :[[@LINE-3]]:39: note: the last parameter in this range is 'iad'
+// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'double' and 'IntAndDouble' can suffer implicit conversions between one another{{$}}
+
+void twoTypes3(unsigned long ul, IntAndDouble iad) {}
+// NO-WARN: Ambiguous constructor call and conversion call, should we take
+// 'int' or 'double' route from 'unsigned long'?
+
+struct IADTypedef {
+ IADTypedef(I);
+ IADTypedef(D);
+ operator I() const;
+ operator D() const;
+};
+
+void twoTypedefs1(int i, IADTypedef iadt) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters for 'twoTypedefs1' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-3]]:37: note: the last parameter in this range is 'iadt'
+// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'int' and 'IADTypedef' can suffer implicit conversions between one another{{$}}
+
+void twoTypedefs2(double d, IADTypedef iadt) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters for 'twoTypedefs2' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:26: note: the first parameter in this range is 'd'
+// CHECK-MESSAGES: :[[@LINE-3]]:40: note: the last parameter in this range is 'iadt'
+// CHECK-MESSAGES: :[[@LINE-4]]:29: note: 'double' and 'IADTypedef' can suffer implicit conversions between one another{{$}}
+
+void twoTypedefs3(unsigned long ul, IADTypedef iadt) {}
+// NO-WARN: Ambiguous constructor call and conversion call, should we take
+// 'int' or 'double' route from 'unsigned long'?
+
+struct Fwd1;
+struct Fwd2;
+void forwards(Fwd1 *f1, Fwd2 *f2) {}
+// NO-WARN and NO-CRASH: Don't try to compare incomplete types.
+
+struct Rec {};
+struct FromRec {
+ FromRec(const Rec &);
+};
+struct ToRec {
+ operator Rec() const;
+};
+struct RecBoth {
+ RecBoth(Rec);
+ operator Rec() const;
+};
+
+void record2record_1(Rec r, FromRec fr) {}
+// NO-WARN: One-way used defined conversion only.
+
+void record2record_2(Rec r, ToRec tr) {}
+// NO-WARN: One-way used defined conversion only.
+
+void record2record_3(Rec r, RecBoth rb) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 2 adjacent parameters for 'record2record_3' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:26: note: the first parameter in this range is 'r'
+// CHECK-MESSAGES: :[[@LINE-3]]:37: note: the last parameter in this range is 'rb'
+// CHECK-MESSAGES: :[[@LINE-4]]:29: note: 'Rec' and 'RecBoth' can suffer implicit conversions between one another{{$}}
+
+void record2record_4(Rec r, FromRec fr, ToRec tr, RecBoth rb) {}
+// NO-WARN: "Rec" and "RecBoth" are not adjacent.
+
+struct X;
+struct Y;
+struct X {
+ X();
+ X(Y);
+ operator Y();
+};
+struct Y {
+ Y();
+ Y(X);
+ operator X();
+};
+void ambiguous_records(X x, Y y) {}
+// NO-WARN: Ambiguous conversion if the arguments are swapped, which results in
+// compiler error: f3(Y{}, X{})
+
+struct Base {};
+struct Derived1 : Base {};
+struct Derived2 : Base {};
+void upcasting(Base *bp, Derived1 *d1p, Derived2 *d2p) {}
+// NO-WARN: Upcasts are unidirectional.
+
+void upcasting_ref(const Base &br, const Derived1 &d1r, const Derived2 &d2r) {}
+
+// This example is weird, because normally * or & upcasts are unidirectional,
+// except when the user explicitly writes converting functions.
+struct WeirdDerived;
+struct WeirdBase {
+ operator WeirdDerived() const;
+};
+struct WeirdDerived : WeirdBase {};
+
+void weird_upcast(const WeirdBase &wbr, const WeirdDerived &wdr) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters for 'weird_upcast' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:36: note: the first parameter in this range is 'wbr'
+// CHECK-MESSAGES: :[[@LINE-3]]:61: note: the last parameter in this range is 'wdr'
+// CHECK-MESSAGES: :[[@LINE-4]]:41: note: at a call site, 'const WeirdDerived &' might bind with same force as 'const WeirdBase &'
+// CHECK-MESSAGES: :[[@LINE-5]]:41: note: 'const WeirdBase &' and 'const WeirdDerived &' can suffer implicit conversions between one another{{$}}
+
+// Reduced case from live crash on OpenCV
+// http://github.com/opencv/opencv/blob/1996ae4a42d7c7cd338fbdd4abbd83b41b448328/modules/core/include/opencv2/core/types.hpp#L173
+template <typename T>
+struct TemplateConversion {
+ TemplateConversion();
+ TemplateConversion(const T &);
+ template <typename T2>
+ operator TemplateConversion<T2>() const;
+};
+typedef TemplateConversion<int> IntConvert;
+typedef TemplateConversion<float> FloatConvert;
+void templated_conversion_to_other_specialisation(FloatConvert fc, IntConvert ic) {}
+// NO-WARN: Template conversion operators are not resolved, we have approximated
+// much of Sema already...
Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.c
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-implicits.c
@@ -0,0 +1,102 @@
+// RUN: %check_clang_tidy %s \
+// RUN: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type %t \
+// RUN: -config='{CheckOptions: [ \
+// RUN: {key: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.ImplicitConversion, value: 1} \
+// RUN: ]}' --
+
+void compare(int a, int b, int c) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: 3 adjacent parameters for 'compare' of similar type ('int') are easily swapped by mistake [experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type]
+// CHECK-MESSAGES: :[[@LINE-2]]:18: note: the first parameter in this range is 'a'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'c'
+
+void b(_Bool b1, _Bool b2, int i) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 3 adjacent parameters for 'b' of convertible types may be easily swapped by mistake [experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type]
+// CHECK-MESSAGES: :[[@LINE-2]]:14: note: the first parameter in this range is 'b1'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-4]]:28: note: '_Bool' and 'int' can suffer implicit conversions between one another{{$}}
+
+void integral1(signed char sc, int si) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral1' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:28: note: the first parameter in this range is 'sc'
+// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'si'
+// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'signed char' and 'int' can suffer implicit conversions between one another{{$}}
+
+void integral2(long l, int i) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral2' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'l'
+// CHECK-MESSAGES: :[[@LINE-3]]:28: note: the last parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'long' and 'int' can suffer implicit conversions between one another{{$}}
+
+void integral3(int i, long l) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'integral3' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:20: note: the first parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-3]]:28: note: the last parameter in this range is 'l'
+// CHECK-MESSAGES: :[[@LINE-4]]:23: note: 'int' and 'long' can suffer implicit conversions between one another{{$}}
+
+void integral4(char c, int i, long l) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 3 adjacent parameters for 'integral4' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:21: note: the first parameter in this range is 'c'
+// CHECK-MESSAGES: :[[@LINE-3]]:36: note: the last parameter in this range is 'l'
+// CHECK-MESSAGES: :[[@LINE-4]]:24: note: 'char' and 'int' can suffer implicit conversions between one another{{$}}
+// CHECK-MESSAGES: :[[@LINE-5]]:31: note: 'char' and 'long' can suffer implicit conversions between one another{{$}}
+
+void floating1(float f, double d) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'floating1' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'f'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in this range is 'd'
+// CHECK-MESSAGES: :[[@LINE-4]]:25: note: 'float' and 'double' can suffer implicit conversions between one another{{$}}
+
+typedef double D;
+void floating2(float f, D d) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent parameters for 'floating2' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:22: note: the first parameter in this range is 'f'
+// CHECK-MESSAGES: :[[@LINE-3]]:27: note: the last parameter in this range is 'd'
+// CHECK-MESSAGES: :[[@LINE-4]]:16: note: after resolving type aliases, type of parameter 'f' is 'float'
+// CHECK-MESSAGES: :[[@LINE-5]]:25: note: after resolving type aliases, type of parameter 'd' is 'double'
+// CHECK-MESSAGES: :[[@LINE-6]]:25: note: 'float' and 'double' can suffer implicit conversions between one another{{$}}
+
+void floatToInt(float f, int i) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent parameters for 'floatToInt' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:23: note: the first parameter in this range is 'f'
+// CHECK-MESSAGES: :[[@LINE-3]]:30: note: the last parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-4]]:26: note: 'float' and 'int' can suffer implicit conversions between one another{{$}}
+
+enum En { A,
+ B };
+
+void unscopedEnumToIntegral(enum En e, int i) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters for 'unscopedEnumToIntegral' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:37: note: the first parameter in this range is 'e'
+// CHECK-MESSAGES: :[[@LINE-3]]:44: note: the last parameter in this range is 'i'
+// CHECK-MESSAGES: :[[@LINE-4]]:40: note: 'enum En' and 'int' can suffer implicit conversions between one another{{$}}
+
+void unscopedEnumToIntegral2(enum En e, unsigned long long ull) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:30: warning: 2 adjacent parameters for 'unscopedEnumToIntegral2' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:38: note: the first parameter in this range is 'e'
+// CHECK-MESSAGES: :[[@LINE-3]]:60: note: the last parameter in this range is 'ull'
+// CHECK-MESSAGES: :[[@LINE-4]]:41: note: 'enum En' and 'unsigned long long' can suffer implicit conversions between one another{{$}}
+
+void unscopedEnum3(char c, enum En e, char c2) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 3 adjacent parameters for 'unscopedEnum3' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:25: note: the first parameter in this range is 'c'
+// CHECK-MESSAGES: :[[@LINE-3]]:44: note: the last parameter in this range is 'c2'
+// CHECK-MESSAGES: :[[@LINE-4]]:28: note: 'char' and 'enum En' can suffer implicit conversions between one another{{$}}
+// CHECK-MESSAGES: :[[@LINE-5]]:39: note: 'enum En' and 'char' can suffer implicit conversions between one another{{$}}
+
+void unscopedEnumToFloating(enum En e, long double ld) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters for 'unscopedEnumToFloating' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:37: note: the first parameter in this range is 'e'
+// CHECK-MESSAGES: :[[@LINE-3]]:52: note: the last parameter in this range is 'ld'
+// CHECK-MESSAGES: :[[@LINE-4]]:40: note: 'enum En' and 'long double' can suffer implicit conversions between one another{{$}}
+
+void unscopedEnumToIntOrFloat(enum En e, int i, float f) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:31: warning: 3 adjacent parameters for 'unscopedEnumToIntOrFloat' of convertible
+// CHECK-MESSAGES: :[[@LINE-2]]:39: note: the first parameter in this range is 'e'
+// CHECK-MESSAGES: :[[@LINE-3]]:55: note: the last parameter in this range is 'f'
+// CHECK-MESSAGES: :[[@LINE-4]]:42: note: 'enum En' and 'int' can suffer implicit conversions between one another{{$}}
+// CHECK-MESSAGES: :[[@LINE-5]]:49: note: 'enum En' and 'float' can suffer implicit conversions between one another{{$}}
+// CHECK-MESSAGES: :[[@LINE-6]]:49: note: 'int' and 'float' can suffer implicit conversions between one another{{$}}
+
+void ptr(int *i, long *l) {}
+// NO-WARN: Though 'int' and 'long' can be converted between one-another
+// implicitly, mixing such pointers is diagnosed by compiler warnings.
Index: clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-cvr-on.cpp
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-cvr-on.cpp
+++ clang-tools-extra/test/clang-tidy/checkers/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type-cvr-on.cpp
@@ -1,5 +1,6 @@
// RUN: %check_clang_tidy %s \
// RUN: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type %t \
+// RUN: --debug --debug-only=AdjacentParameters \
// RUN: -config='{CheckOptions: [ \
// RUN: {key: experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.CVRMixPossible, value: 1} \
// RUN: ]}' --
Index: clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst
+++ clang-tools-extra/docs/clang-tidy/checks/experimental-cppcoreguidelines-avoid-adjacent-parameters-of-the-same-type.rst
@@ -99,6 +99,38 @@
struct T {};
void f(T *tp, const T *ctp) {}
+.. option:: ImplicitConversion
+
+ Whether to allow the approximation of some usual
+ `implicit conversions <http://en.cppreference.com/w/cpp/language/implicit_conversion>`_
+ when considering which type might be potentially mixed up with another.
+ If `0` (default value), the check does not consider **any** implicit
+ conversions.
+ A non-zero value turns this **on**.
+ A non-zero value will in almost all cases produce a **significantly broader**
+ set of results.
+
+ .. warning::
+ Turning the modelling of implicit conversion sequences on
+ relaxes the constraints for "type convertibility" significantly,
+ however, it also applies a generous performance hit on the check's cost.
+ The check will have to explore a **polynomially more** possibilities:
+ O(n\ :sup:`2`\ ) instead of O(n) for each function's ``n`` parameters.
+ The emitted diagnostics will also be more verbose, which might take more
+ time to stringify.
+
+ It is advised to normally leave this option *off*.
+
+ For details on what is matched by this option, see
+ `Implicit conversion modelling`_.
+
+ The following examples will not produce a diagnostic unless
+ *ImplicitConversion* is set to a non-zero value.
+
+ .. code-block:: c++
+
+ void f(char c, int i, double d) {}
+
Limitations
-----------
@@ -133,10 +165,10 @@
template <typename T>
struct vector {
- typedef T element_type;
- typedef const T const_element_type;
- typedef T & reference_type;
- typedef const T & const_reference_type;
+ typename T element_type;
+ typename const T const_element_type;
+ typename T & reference_type;
+ typename const T & const_reference_type;
};
// Finds the longest occurrence's length between elements "RightEnd"
@@ -148,3 +180,34 @@
const vector<T> & Vector,
typename vector<T>::const_reference_type RightEnd,
const typename vector<T>::element_type & LeftBegin) { /* ... */ }
+
+
+Implicit conversion modelling
+-----------------------------
+
+Given a function ``void f(T1 t, T2 u)``, unless ``T1`` and ``T2`` are the same,
+the check won't warn in *"default"* mode. If ``ImplicitConversion`` is turned on
+(see Options_), and either ``T1``
+`can be converted to <https://en.cppreference.com/w/cpp/language/implicit_conversion>`_
+``T2``, or vice versa, diagnostics will be made.
+
+In the case of the following function, the numeric parameters can be
+converted between one another.
+``SomeEnum`` is convertible to any numeric type.
+Thus, an "adjacent parameter range" of **4** is diagnosed here.
+
+.. code-blocK:: c++
+
+ enum SomeEnum { /* ... */ };
+ void SomeFunction(int, float, double, unsigned long, SomeEnum) {}
+
+Currently, the following implicit conversions are modelled:
+
+ - *standard conversion* sequences:
+ - numeric promotion and conversion between integer (including ``enum``) and
+ floating types, considered essentially the same under the umbrella term
+ "conversion"
+ - For **C** projects, the numeric conversion rules are relaxed to conform
+ to C rules.
+ - *user defined conversions*: non-``explicit`` converting constructors and
+ conversion operators.
Index: clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h
===================================================================
--- clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h
+++ clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.h
@@ -47,6 +47,10 @@
/// Whether to consider 'T' and 'const T'/'volatile T'/etc. arguments to be
/// possible mixup.
const bool CVRMixPossible;
+
+ /// Whether to consider implicit conversion possibilities as a potential match
+ /// for adjacency.
+ const bool ImplicitConversion;
};
} // namespace experimental
Index: clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp
===================================================================
--- clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp
+++ clang-tools-extra/clang-tidy/experimental/CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck.cpp
@@ -11,6 +11,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/BitmaskEnum.h"
+#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include <string>
@@ -21,6 +22,29 @@
#define DEBUG_TYPE "AdjacentParameters"
#include "llvm/Support/Debug.h"
// clang-format on
+template <std::size_t Width>
+static inline std::string format_bytes_do(std::size_t N) {
+ static_assert(std::is_unsigned<decltype(N)>::value,
+ "size_t is not unsigned?!");
+ constexpr std::size_t WidthWithPadding = Width + (Width / 4);
+ char C[WidthWithPadding];
+ for (std::size_t CurPos = 0; CurPos < WidthWithPadding; ++CurPos) {
+ if (CurPos % 5 == 0) {
+ // Group digits by 4.
+ C[CurPos] = ' ';
+ continue;
+ }
+
+ C[CurPos] = N % 2 ? '1' : '0';
+ N >>= 1;
+ }
+
+ return std::string{std::rbegin(C), std::rend(C)};
+}
+
+template <typename T> static inline std::string format_bytes(T &&t) {
+ return format_bytes_do<sizeof(T) * 8>(t);
+}
using namespace clang::ast_matchers;
@@ -37,60 +61,101 @@
// Set the bit at index N to 1 as the enum constant. N = 0 is invalid.
#define BIT(Name, N) MIXUP_##Name = (1ull << (N##ull - 1ull))
- BIT(None, 1), //< Mixup is not possible.
- BIT(Trivial, 2), //< No extra information needed.
- BIT(Typedef, 3), //< Parameter of a typedef which resolves to an effective
- //< desugared type same as the other arg.
- BIT(RefBind, 4), //< Parameter mixes with another due to reference binding.
- BIT(CVR, 5), //< Parameter mixes with another through implicit
- //< qualification.
+ BIT(None, 1), //< Mixup is not possible.
+ BIT(Trivial, 2), //< No extra information needed.
+ BIT(Typedef, 3), //< Parameter of a typedef which resolves to an effective
+ //< desugared type same as the other arg.
+ BIT(RefBind, 4), //< Parameter mixes with another due to reference binding.
+ BIT(CVR, 5), //< Parameter mixes with another through implicit
+ //< qualification.
+ BIT(Implicit, 6), //< An implicit conversion may happen.
#undef BIT
- LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ MIXUP_CVR)
+ LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ MIXUP_Implicit)
};
+#undef FBIT
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
-/// A named tuple that contains which parameter with which other parameter
-/// can be mixed up in what fashion.
-struct Mixup {
- const ParmVarDecl *First, *Second;
- MixupTag Flags;
+/// Implicit conversion sequence steps resulting in types referred here.
+struct ConversionSequence {
+ /// Type of the intermediate value after the first standard conversion.
+ const Type *StdPre;
+ /// Type of the intermediate value after executing the user-defined conversion
+ /// which, in case of constructors, the user type, in case of conversion
+ /// operators, the result of the operator.
+ const Type *UserType;
+ /// Type of the intermediate value after the second standard conversion.
+ const Type *StdPost;
+
+ ConversionSequence() : StdPre(nullptr), UserType(nullptr), StdPost(nullptr) {}
+
+ explicit operator bool() const { return StdPre || UserType || StdPost; }
+
+ /// Whether the conversion sequence is single-step only.
+ bool single() const {
+ return ((bool)StdPre ^ (bool)UserType ^ (bool)StdPost) &&
+ !(StdPre && UserType && StdPost);
+ }
+};
+
+struct MixupData {
+ MixupTag Flag;
+ ConversionSequence ConvLTR, ConvRTL;
- Mixup(const ParmVarDecl *A, const ParmVarDecl *B, MixupTag Flags)
- : First(A), Second(B), Flags(Flags) {}
+ MixupData(MixupTag Flags) : Flag(Flags) {}
+ MixupData(MixupTag Flags, const ConversionSequence &Seq)
+ : Flag(Flags), ConvLTR(Seq), ConvRTL(Seq) {}
+ MixupData(MixupTag Flags, const ConversionSequence <R,
+ const ConversionSequence &RTL)
+ : Flag(Flags), ConvLTR(LTR), ConvRTL(RTL) {}
- Mixup operator|(MixupTag EnableFlags) const {
- return {First, Second, Flags | EnableFlags};
+ MixupData operator|(MixupTag EnableFlag) const {
+ return {Flag | EnableFlag, ConvLTR, ConvRTL};
}
- Mixup &operator|=(MixupTag EnableFlags) {
- Flags |= EnableFlags;
+ MixupData &operator|=(MixupTag EnableFlag) {
+ Flag |= EnableFlag;
return *this;
}
/// Sanitises the Mixup's flags so it doesn't contain contradictory bits.
void sanitise() {
- assert(Flags != MIXUP_Invalid &&
+ assert(Flag != MIXUP_Invalid &&
"Mixup tag had full zero bit pattern value!");
- if (Flags & MIXUP_None) {
+ if (Flag & MIXUP_None) {
// If at any point the checks mark the mixup impossible, it is just simply
// impossible.
- Flags = MIXUP_None;
+ Flag = MIXUP_None;
return;
}
- if (Flags == MIXUP_Trivial)
+ if (Flag == MIXUP_Trivial)
return;
- if (Flags ^ MIXUP_Trivial)
+ if (Flag ^ MIXUP_Trivial)
// If any other bits than Trivial is set, unset Trivial, so only the
// annotation bits warranting extra diagnostic are set.
- Flags &= ~MIXUP_Trivial;
+ Flag &= ~MIXUP_Trivial;
+
+ // Set LTR and RTL implicity according to the members being set.
+ if (ConvLTR && ConvRTL)
+ Flag |= MIXUP_Implicit;
+ else
+ Flag &= ~MIXUP_Implicit;
}
};
+/// Named tuple that contains that the types of the arguments From and To
+/// are mixable with the given flags in a particular fashion.
+struct Mixup {
+ const ParmVarDecl *First, *Second;
+ MixupData Data;
+
+ Mixup(const ParmVarDecl *A, const ParmVarDecl *B, MixupData Data)
+ : First(A), Second(B), Data(Data) {}
+};
static_assert(std::is_trivially_copyable<Mixup>::value,
- "keep Mixup trivially copyable!");
+ "keep Mixup and components trivially copyable!");
/// Represents a (closed) range of adjacent parameters that can be mixed up at
/// a call site.
@@ -118,19 +183,33 @@
} // namespace
-/// Returns whether an lvalue reference refers to the same type as T.
-static MixupTag RefBindsToSameType(const LValueReferenceType *LRef,
- const Type *T, bool CVRMixPossible);
-
-/// Returns whether LType and RType refer to the same type in a sense that at a
-/// call site it is possible to mix the types up if the actual arguments are
-/// specified in opposite order.
-/// \returns MixupTag indicating how a mixup between the arguments happens.
-/// The final output of this potentially recursive function must be sanitised.
-static MixupTag HowPossibleToMixUpAtCallSite(const QualType LType,
- const QualType RType,
- const ASTContext &Ctx,
- const bool CVRMixPossible) {
+/// Returns how an lvalue reference refers to the same type as T.
+static MixupData RefBindsToSameType(const LValueReferenceType *LRef,
+ const Type *T, const ASTContext &Ctx,
+ bool IsRefRightType, bool CVRMixPossible,
+ bool ImplicitConversion);
+
+/// Returns whether the left side type is convertible to the right side type,
+/// by attempting to approximate implicit conversion sequences.
+/// \param AllowUserDefined If false, only standard conversions will be
+/// approximated.
+/// \note The result of this operation is not symmetric!
+static MixupData HowConvertible(const Type *LT, const Type *RT,
+ const LangOptions &LOpts,
+ bool AllowUserDefined = true);
+
+/// Returns how LType and RType may essentially refer to the same type - in a
+/// sense that at a call site it is possible to mix the arguments up if
+/// specified in the opposite order.
+/// \returns MixupData indicating how a mixup between the arguments happens.
+/// \note The final output of this potentially recursive function must be
+/// sanitised by 'sanitiseMixup' before it could be used, to ensure only the
+/// proper bits are set!
+static MixupData HowPossibleToMixUpAtCallSite(const QualType LType,
+ const QualType RType,
+ const ASTContext &Ctx,
+ const bool CVRMixPossible,
+ const bool ImplicitConversion) {
LLVM_DEBUG(llvm::dbgs() << "HowPossibleToMixUpAtCallSite?\n Left:";
LType.dump(llvm::dbgs()); llvm::dbgs() << "\n\t and \n Right: ";
RType.dump(llvm::dbgs()); llvm::dbgs() << '\n';);
@@ -141,12 +220,18 @@
}
// Remove certain sugars that don't affect mixability from the types.
- if (dyn_cast<const ParenType>(LType.getTypePtr()))
+ if (dyn_cast<const ParenType>(LType.getTypePtr())) {
+ LLVM_DEBUG(llvm::dbgs() << "Left is some sugared Parens()\n");
return HowPossibleToMixUpAtCallSite(LType.getSingleStepDesugaredType(Ctx),
- RType, Ctx, CVRMixPossible);
- if (dyn_cast<const ParenType>(RType.getTypePtr()))
+ RType, Ctx, CVRMixPossible,
+ ImplicitConversion);
+ }
+ if (dyn_cast<const ParenType>(RType.getTypePtr())) {
+ LLVM_DEBUG(llvm::dbgs() << "Right is some sugared Parens()\n");
return HowPossibleToMixUpAtCallSite(
- LType, RType.getSingleStepDesugaredType(Ctx), Ctx, CVRMixPossible);
+ LType, RType.getSingleStepDesugaredType(Ctx), Ctx, CVRMixPossible,
+ ImplicitConversion);
+ }
// An argument of type 'T' and 'const T &' may bind with the same power.
// (Note this is a different case, as 'const T &' is a '&' on the top level,
@@ -156,37 +241,46 @@
if (LType->isLValueReferenceType()) {
LLVM_DEBUG(llvm::dbgs() << "!!! Left is an lvalue reference type\n";);
- MixupTag RefBind = RefBindsToSameType(LType->getAs<LValueReferenceType>(),
- RType.getTypePtr(), CVRMixPossible);
- // RefBind may or may not have given us a tag (e.g. reference was to a
- // typedef) via a recursive chain back to this function. Apply the
- // "bind power" tag here to indicate a reference binding happened.
- // (If RefBind was MIXUP_None, a later sanitise step will undo every bit
- // except for None.)
- return RefBind | MIXUP_RefBind;
+ // Return value of function call may or may not have given us a tag (e.g.
+ // reference was to a typedef) via a recursive chain back to this
+ // function. Apply the "bind power" tag here to indicate a reference
+ // binding happened. (If RefBind was MIXUP_None, a later sanitise step
+ // will undo every bit except for None.)
+ return RefBindsToSameType(LType->getAs<LValueReferenceType>(),
+ RType.getTypePtr(), Ctx,
+ /* IsRefRightType =*/false, CVRMixPossible,
+ ImplicitConversion) |
+ MIXUP_RefBind;
}
if (RType->isLValueReferenceType()) {
LLVM_DEBUG(llvm::dbgs() << "!!! Right is an lvalue reference type.\n";);
- MixupTag RefBind = RefBindsToSameType(RType->getAs<LValueReferenceType>(),
- LType.getTypePtr(), CVRMixPossible);
- return RefBind | MIXUP_RefBind;
+ return RefBindsToSameType(RType->getAs<LValueReferenceType>(),
+ LType.getTypePtr(), Ctx,
+ /* IsRefRightType =*/true, CVRMixPossible,
+ ImplicitConversion) |
+ MIXUP_RefBind;
}
}
- // A parameter of type 'T' and 'const T' may bind with the same power.
+ // An argument of type 'T' and 'const T' may bind with the same power.
// Case for both types being const qualified (for the same type) is handled
// by LType == RType.
if (LType.getLocalCVRQualifiers() || RType.getLocalCVRQualifiers()) {
- LLVM_DEBUG(llvm::dbgs()
- << "!!! Left or right is locally CVR: Left: "
- << LType.getLocalQualifiers().getCVRQualifiers() << ", Right: "
- << RType.getLocalQualifiers().getCVRQualifiers() << '\n');
+ LLVM_DEBUG(
+ llvm::dbgs()
+ << "!!! Left or right is locally CVR: Left: "
+ << format_bytes(
+ (unsigned char)LType.getLocalQualifiers().getCVRQualifiers())
+ << ", Right: "
+ << format_bytes(
+ (unsigned char)RType.getLocalQualifiers().getCVRQualifiers())
+ << '\n');
if (!CVRMixPossible)
return MIXUP_None;
return HowPossibleToMixUpAtCallSite(LType.getUnqualifiedType(),
RType.getUnqualifiedType(), Ctx,
- CVRMixPossible) |
+ CVRMixPossible, ImplicitConversion) |
MIXUP_CVR;
}
@@ -196,57 +290,86 @@
if (LTypedef && RTypedef) {
LLVM_DEBUG(llvm::dbgs()
<< "!!! Both Left and Right are typedef types.\n";);
- return MIXUP_Typedef | HowPossibleToMixUpAtCallSite(LTypedef->desugar(),
- RTypedef->desugar(),
- Ctx, CVRMixPossible);
+ return HowPossibleToMixUpAtCallSite(LTypedef->desugar(),
+ RTypedef->desugar(), Ctx,
+ CVRMixPossible, ImplicitConversion) |
+ MIXUP_Typedef;
}
if (LTypedef) {
LLVM_DEBUG(llvm::dbgs() << "!!! Left is typedef type.\n";);
- return MIXUP_Typedef |
- HowPossibleToMixUpAtCallSite(LTypedef->desugar(), RType, Ctx,
- CVRMixPossible);
+ return HowPossibleToMixUpAtCallSite(LTypedef->desugar(), RType, Ctx,
+ CVRMixPossible, ImplicitConversion) |
+ MIXUP_Typedef;
}
if (RTypedef) {
LLVM_DEBUG(llvm::dbgs() << "!!! Right is typedef type.\n";);
- return MIXUP_Typedef |
- HowPossibleToMixUpAtCallSite(LType, RTypedef->desugar(), Ctx,
- CVRMixPossible);
+ return HowPossibleToMixUpAtCallSite(LType, RTypedef->desugar(), Ctx,
+ CVRMixPossible, ImplicitConversion) |
+ MIXUP_Typedef;
}
}
if (LType->isPointerType() && RType->isPointerType()) {
// (Both types being the exact same pointer is handled by LType == RType.)
+ // The implicit conversion between any T* and U* possible in C is ignored,
+ // as virtually all compilers emit a warning if the conversion is done at a
+ // call site.
LLVM_DEBUG(llvm::dbgs() << "!!! Both Left and Right are pointer types.\n";);
+ // Implicit conversions of two pointers should be diagnosed if they point to
+ // C++ records to find Derived-To-Base implicit casts.
+ // For non-user-types, do not check - giving an unrelated, e.g. "long *" in
+ // place of an "int *" is diagnosed anyways by warnings or errors.
+ bool ShouldDiagnoseImplicitBehindPtr = ImplicitConversion &&
+ LType->getPointeeCXXRecordDecl() &&
+ RType->getPointeeCXXRecordDecl();
+ LLVM_DEBUG(
+ llvm::dbgs()
+ << "Checking mixup between pointed types. Allowing implicit modelling? "
+ << ShouldDiagnoseImplicitBehindPtr << "\nOriginally the user specified "
+ << ImplicitConversion << ".\n");
return HowPossibleToMixUpAtCallSite(
- LType->getPointeeType(), RType->getPointeeType(), Ctx, CVRMixPossible);
+ LType->getPointeeType(), RType->getPointeeType(), Ctx, CVRMixPossible,
+ ShouldDiagnoseImplicitBehindPtr);
}
- // A parameter of type 'T' and 'const T' may bind with the same power.
- // Case for both types being const qualified (for the same type) is handled
- // by LType == RType.
- if (CVRMixPossible &&
- (LType.isLocalConstQualified() || LType.isLocalVolatileQualified())) {
- LLVM_DEBUG(llvm::dbgs() << "!!! Left is CV qualified.";
- LType.dump(llvm::dbgs()); llvm::dbgs() << '\n';);
- return MIXUP_CVR | HowPossibleToMixUpAtCallSite(LType.getUnqualifiedType(),
- RType, Ctx, CVRMixPossible);
- }
- if (CVRMixPossible &&
- (RType.isLocalConstQualified() || RType.isLocalVolatileQualified())) {
- LLVM_DEBUG(llvm::dbgs() << "!!! Right is CV qualified\n";
- RType.dump(llvm::dbgs()); llvm::dbgs() << '\n';);
- return MIXUP_CVR |
- HowPossibleToMixUpAtCallSite(LType, RType.getUnqualifiedType(), Ctx,
- CVRMixPossible);
- }
-
- LLVM_DEBUG(llvm::dbgs() << "<<< End of logic reached, types don't match.";);
+ if (ImplicitConversion) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "Implicit conversion check has been enabled! Checking...\n");
+
+ const Type *LT = LType.getTypePtr();
+ const Type *RT = RType.getTypePtr();
+
+ // Try approximating an implicit conversion sequence.
+ LLVM_DEBUG(llvm::dbgs() << "?????????? Check Left->Right ..............\n");
+ MixupData LTR =
+ HowConvertible(LT, RT, Ctx.getLangOpts(), /* AllowUserDefined =*/true);
+ LLVM_DEBUG(llvm::dbgs()
+ << "Left->Right flags: " << format_bytes(LTR.Flag) << '\n');
+ if (LTR.Flag != MIXUP_None)
+ LLVM_DEBUG(llvm::dbgs() << "Left -> Right mixup\n");
+
+ LLVM_DEBUG(llvm::dbgs() << "?????????? Check Right->Left ..............\n");
+ MixupData RTL =
+ HowConvertible(RT, LT, Ctx.getLangOpts(), /* AllowUserDefined =*/true);
+ LLVM_DEBUG(llvm::dbgs()
+ << "Left<-Right flags: " << format_bytes(RTL.Flag) << '\n');
+ if (RTL.Flag != MIXUP_None)
+ LLVM_DEBUG(llvm::dbgs() << "Left <- Right mixup\n");
+
+ if (LTR.ConvLTR && RTL.ConvRTL)
+ return {MIXUP_Implicit, LTR.ConvLTR, RTL.ConvRTL};
+ }
+
+ LLVM_DEBUG(llvm::dbgs() << "<<< End of logic reached, types don't match.\n";);
return MIXUP_None;
}
-static MixupTag RefBindsToSameType(const LValueReferenceType *LRef,
- const Type *T, bool CVRMixPossible) {
- LLVM_DEBUG(llvm::dbgs() << "RefBindsToSameType?\n"; LRef->dump(llvm::dbgs());
+static MixupData RefBindsToSameType(const LValueReferenceType *LRef,
+ const Type *T, const ASTContext &Ctx,
+ const bool IsRefRightType,
+ const bool CVRMixPossible,
+ const bool ImplicitConversion) {
+ LLVM_DEBUG(llvm::dbgs() << "refBindsToSameType?\n"; LRef->dump(llvm::dbgs());
llvm::dbgs() << "\n\t and \n"; T->dump(llvm::dbgs());
llvm::dbgs() << '\n';);
@@ -260,18 +383,379 @@
return MIXUP_None;
}
- if (const auto *TypedefTy = ReferredType.getTypePtr()->getAs<TypedefType>()) {
+ if (const auto *TypedefTy = ReferredType->getAs<TypedefType>()) {
// If the referred type is a typedef, try checking the mixup-chance on the
// desugared type.
LLVM_DEBUG(llvm::dbgs() << "Reference to a typedef, calling back...\n";);
- return HowPossibleToMixUpAtCallSite(TypedefTy->desugar(), QualType{T, 0},
- TypedefTy->getDecl()->getASTContext(),
- CVRMixPossible);
+ if (!IsRefRightType)
+ return HowPossibleToMixUpAtCallSite(TypedefTy->desugar(), QualType{T, 0},
+ Ctx, CVRMixPossible,
+ ImplicitConversion);
+ return HowPossibleToMixUpAtCallSite(QualType{T, 0}, TypedefTy->desugar(),
+ Ctx, CVRMixPossible,
+ ImplicitConversion);
}
LLVM_DEBUG(llvm::dbgs() << "is referred type type_ptr() and T the same? "
<< (ReferredType.getTypePtr() == T) << '\n';);
- return ReferredType.getTypePtr() == T ? MIXUP_Trivial : MIXUP_None;
+ if (ReferredType.getTypePtr() == T)
+ return MIXUP_Trivial;
+ if (ImplicitConversion) {
+ // Try to see if the reference can be bound through an implicit conversion.
+ if (!IsRefRightType)
+ return HowPossibleToMixUpAtCallSite(ReferredType.getUnqualifiedType(),
+ QualType{T, 0}, Ctx, CVRMixPossible,
+ ImplicitConversion);
+ return HowPossibleToMixUpAtCallSite(QualType{T, 0},
+ ReferredType.getUnqualifiedType(), Ctx,
+ CVRMixPossible, ImplicitConversion);
+ }
+ return MIXUP_None;
+}
+
+static inline bool IsNumericConvertible(const Type *LT, const Type *RT) {
+ LLVM_DEBUG(llvm::dbgs() << "IsNumericConvertible(" << LT << ", " << RT
+ << ")\n");
+ if (!LT || !RT)
+ return false;
+
+ bool LI = LT->isIntegerType();
+ bool LF = LT->isFloatingType();
+ bool RI = RT->isIntegerType();
+ bool RF = RT->isFloatingType();
+
+ // Through promotions and conversions, there is free passage between
+ // "integer types" and "floating types".
+ // Promotions don't change value, conversions may result in value or precision
+ // loss, but for the sake of easy understanding, we put the two under
+ // one umbrella.
+ return (LI && RI) || (LF && RF) || (LI && RF) || (LF && RI);
+}
+
+/// Returns a Mixup indicating if the specified converting constructor can be
+/// called with LT or after applying a standard conversion sequence on LT.
+static MixupData TryConvertingConstructor(const Type *LT,
+ const CXXRecordDecl *RD,
+ const CXXConstructorDecl *Conv,
+ const LangOptions &LOpts) {
+ const ParmVarDecl *ConArg = Conv->getParamDecl(0);
+ MixupData Mix = HowPossibleToMixUpAtCallSite(
+ QualType{LT, 0}, ConArg->getType(), Conv->getASTContext(),
+ // Model qualifier mixing generously.
+ /* CVRMixPossible =*/true,
+ // Do not allow any further implicit conversions.
+ /* ImplicitConversion =*/false);
+ LLVM_DEBUG(llvm::dbgs() << "Conv no-std (pre sanitise): "
+ << format_bytes(Mix.Flag) << '\n');
+ Mix.sanitise();
+ LLVM_DEBUG(llvm::dbgs() << "Conv no-std (post sanitise): "
+ << format_bytes(Mix.Flag) << '\n');
+ if (Mix.Flag != MIXUP_None) {
+ // If simply applying the user-defined constructor works as is, this is
+ // the right constructor.
+ LLVM_DEBUG(llvm::dbgs() << "Conversion constructor matches exactly.\n");
+ ConversionSequence Seq;
+ Seq.UserType = RD->getTypeForDecl();
+ Mix.ConvLTR = Seq;
+ return Mix;
+ }
+
+ // Otherwise, see if LT and the converting constructor's argument can be
+ // matched via a standard conversion before. This will take care of converting
+ // an int to another type.
+ ConversionSequence Seq;
+ MixupData Pre = HowConvertible(LT, ConArg->getType().getTypePtr(), LOpts,
+ /* AllowUserDefined =*/false);
+ LLVM_DEBUG(llvm::dbgs() << "Pre-UD: " << format_bytes(Pre.Flag)
+ << " to type \n";
+ Pre.ConvLTR.StdPre->dump(llvm::dbgs()); llvm::dbgs() << '\n');
+ if (Pre.Flag == MIXUP_None || Pre.Flag == MIXUP_Trivial)
+ // If not possible to mix, don't check. If trivial, previous case should
+ // have returned already.
+ return MIXUP_None;
+ Seq.StdPre = Pre.ConvLTR.StdPre;
+
+ LLVM_DEBUG(llvm::dbgs()
+ << "Conversion constructor matches after standard conversion.\n");
+
+ Mix = HowPossibleToMixUpAtCallSite(
+ QualType{Seq.StdPre, 0}, ConArg->getType(), Conv->getASTContext(),
+ // Model qualifier mixing generously.
+ /* CVRMixPossible =*/true,
+ // Do not allow any further implicit conversions.
+ /* ImplicitConversion =*/false);
+ LLVM_DEBUG(llvm::dbgs() << "Conv (pre sanitise): " << format_bytes(Mix.Flag)
+ << '\n');
+ Mix.sanitise();
+ LLVM_DEBUG(llvm::dbgs() << "Conv (post sanitise): " << format_bytes(Mix.Flag)
+ << '\n');
+ if (Mix.Flag != MIXUP_None) {
+ LLVM_DEBUG(llvm::dbgs() << "Conversion constructor matches!\n");
+ Seq.UserType = RD->getTypeForDecl();
+ Mix.ConvLTR = Seq;
+ return Mix;
+ }
+
+ return MIXUP_None;
+}
+
+static MixupData TryConversionOperator(const CXXConversionDecl *Conv,
+ const Type *RT,
+ const LangOptions &LOpts) {
+ MixupData Mix = HowPossibleToMixUpAtCallSite(
+ Conv->getConversionType(), QualType{RT, 0}, Conv->getASTContext(),
+ // Model qualifier mixing generously.
+ /* CVRMixPossible =*/true,
+ // Do not allow any further implicit conversions.
+ /* ImplicitConversion =*/false);
+ LLVM_DEBUG(llvm::dbgs() << "Conv no-std (pre sanitise): "
+ << format_bytes(Mix.Flag) << '\n');
+ Mix.sanitise();
+ LLVM_DEBUG(llvm::dbgs() << "Conv no-std (post sanitise): "
+ << format_bytes(Mix.Flag) << '\n');
+ if (Mix.Flag != MIXUP_None) {
+ // If simply applying the user-defined operator works as is, this is
+ // the right conversion operator.
+ ConversionSequence Seq;
+ Seq.UserType = RT;
+ Mix.ConvLTR = Seq;
+ return Mix;
+ }
+
+ // Otherwise, see if the converting operator's result and RT can be
+ // matched via a standard conversion after. This will take care of converting
+ // a result of float to another type.
+ ConversionSequence Seq;
+ MixupData Post = HowConvertible(Conv->getConversionType().getTypePtr(), RT,
+ LOpts, /* AllowUserDefined =*/false);
+ LLVM_DEBUG(llvm::dbgs() << "Post-UD: " << format_bytes(Post.Flag)
+ << " to type \n";
+ Post.ConvLTR.StdPre->dump(llvm::dbgs()); llvm::dbgs() << '\n');
+ if (Post.Flag == MIXUP_None || Post.Flag == MIXUP_Trivial)
+ // If not possible to mix, don't check. If trivial, previous case should
+ // have returned already.
+ return MIXUP_None;
+ // Save the result type of the user-defined conversion operator.
+ Seq.UserType = Conv->getConversionType().getTypePtr();
+
+ // The unqualified version of the conversion operator's return type can be
+ // converted to the "needed" Right-Type (output type wanted by the caller
+ // of this method). Now see if between we lost any qualifiers.
+ Mix = HowPossibleToMixUpAtCallSite(
+ Conv->getConversionType(),
+ QualType{Conv->getConversionType().getTypePtr(), 0},
+ Conv->getASTContext(),
+ // Model qualifier mixing generously.
+ /* CVRMixPossible =*/true,
+ // Do not allow any further implicit conversions.
+ /* ImplicitConversion =*/false);
+ LLVM_DEBUG(llvm::dbgs() << "Conv (pre sanitise): " << format_bytes(Mix.Flag)
+ << '\n');
+ Mix.sanitise();
+ LLVM_DEBUG(llvm::dbgs() << "Conv (post sanitise): " << format_bytes(Mix.Flag)
+ << '\n');
+ if (Mix.Flag != MIXUP_None) {
+ LLVM_DEBUG(llvm::dbgs() << "Conversion operator matches!\n");
+ Seq.StdPost = RT;
+ Mix.ConvLTR = Seq;
+ return Mix;
+ }
+
+ return MIXUP_None;
+}
+
+static MixupData HowConvertible(const Type *LT, const Type *RT,
+ const LangOptions &LOpts,
+ bool AllowUserDefined) {
+ LLVM_DEBUG(llvm::dbgs() << ">>>>> How convertible? \n";
+ LT->dump(llvm::dbgs()); llvm::dbgs() << '\n';
+ RT->dump(llvm::dbgs()); llvm::dbgs() << '\n');
+ if (LT == RT) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "Left and right is the same, returning Trivial.\n");
+ return MIXUP_Trivial;
+ }
+
+ ConversionSequence Seq;
+
+ const auto *LBT = LT->getAs<BuiltinType>();
+ const auto *RBT = RT->getAs<BuiltinType>();
+ if (LBT && RBT && LBT == RBT)
+ return {MIXUP_Trivial};
+ if (IsNumericConvertible(LBT, RBT)) {
+ // Builtin numerical types are back-and-forth convertible.
+ LLVM_DEBUG(llvm::dbgs() << "Builtin conversion between numerics.\n");
+ Seq.StdPre = RBT;
+ return {MIXUP_Implicit, Seq};
+ }
+
+ const auto *LET = LT->getAs<EnumType>();
+ const auto *RET = RT->getAs<EnumType>();
+ if (LET && !LET->isScopedEnumeralType() && RBT &&
+ (RBT->isIntegerType() || RBT->isFloatingType())) {
+ // Enum can convert to underlying integer type.
+ LLVM_DEBUG(llvm::dbgs() << "Enum -> integer/float.\n");
+ Seq.StdPre = RBT;
+ return {MIXUP_Implicit, Seq};
+ }
+ if (LBT && (LBT->isFloatingType() || LBT->isIntegerType()) && RET) {
+ // Enum cannot be constructed from any builtin type (neither int, nor
+ // float).
+ if (LOpts.CPlusPlus) {
+ LLVM_DEBUG(llvm::dbgs() << "Numeric -/-> Enum NOT POSSIBLE (C++).\n");
+ return MIXUP_None;
+ }
+ // In C, you can go back and forth between numeric types.
+ Seq.StdPre = RET;
+ return {MIXUP_Implicit, Seq};
+ }
+
+ if (LOpts.CPlusPlus) {
+ // We are checking Left -> Right mixup, in which case Left <: Right should
+ // hold for DerivedToBase implicit cast. This is a standard implicit
+ // conversion.
+ const auto *LCXXRec = LT->getAsCXXRecordDecl();
+ const auto *RCXXRec = RT->getAsCXXRecordDecl();
+ LLVM_DEBUG(llvm::dbgs() << "Left as record: " << LCXXRec
+ << ", Right as record: " << RCXXRec << '\n');
+ if (LCXXRec && RCXXRec && LCXXRec->isCompleteDefinition() &&
+ RCXXRec->isCompleteDefinition() &&
+ (LCXXRec->isDerivedFrom(RCXXRec) ||
+ LCXXRec->isVirtuallyDerivedFrom(RCXXRec))) {
+ LLVM_DEBUG(llvm::dbgs() << "Left <: Right, giving standard DerivedToBase "
+ "cast Left->Right.\n");
+ Seq.StdPre = RT;
+ return {MIXUP_Implicit, Seq};
+ }
+ }
+
+ LLVM_DEBUG(llvm::dbgs() << "Before checking user defined...\n");
+
+ if (!LOpts.CPlusPlus || !AllowUserDefined)
+ // User-defined conversions are only sensible in C++ mode.
+ return MIXUP_None;
+ assert(!Seq && "Non-user defined conversion check should've returned.");
+
+ LLVM_DEBUG(llvm::dbgs() << "Checking for user-defined conversions...\n");
+ MixupTag UserMixup = MIXUP_Invalid;
+ bool FoundConvertingCtor = false, FoundConversionOperator = false;
+ bool FoundMultipleMatches = false;
+ // An implicit conversion sequence may only contain at most *one* user-defined
+ // conversion.
+ if (const auto *RRT = RT->getAs<RecordType>()) {
+ LLVM_DEBUG(llvm::dbgs() << "Right hand side is a record type.\n");
+ const auto *RD = RRT->getAsCXXRecordDecl();
+ if (!RD)
+ // Initialisation of C-style record types from an unrelated type is not
+ // possible.
+ return MIXUP_None;
+ if (!RD->isCompleteDefinition())
+ // Incomplete definition means we don't know anything about members.
+ return MIXUP_None;
+
+ for (const CXXConstructorDecl *Con : RD->ctors()) {
+ if (Con->isCopyOrMoveConstructor() ||
+ !Con->isConvertingConstructor(/* AllowExplicit =*/false))
+ continue;
+ LLVM_DEBUG(llvm::dbgs() << "Found a converting constructor? \n";
+ Con->dump(llvm::dbgs()); llvm::dbgs() << '\n');
+
+ MixupData ConvertingResult = TryConvertingConstructor(LT, RD, Con, LOpts);
+ if (ConvertingResult.Flag == MIXUP_None)
+ continue;
+
+ LLVM_DEBUG(llvm::dbgs() << "Came back with a positive result.\n");
+ FoundConvertingCtor = true;
+
+ if (!ConvertingResult.ConvLTR.StdPre) {
+ // A match without a pre-conversion was found for the converting ctor.
+ LLVM_DEBUG(llvm::dbgs() << "! EXACT MATCH.\n");
+ UserMixup |= ConvertingResult.Flag;
+ Seq = ConvertingResult.ConvLTR;
+ FoundMultipleMatches = false;
+ break;
+ }
+
+ if (!FoundMultipleMatches && Seq) {
+ // If the loop executes multiple times, it's possible to find multiple
+ // converting constructors with different, ambiguous conversion
+ // sequences leading up to them. This is an error, but we need to check
+ // all constructors first for a possible exact match.
+ FoundMultipleMatches = true;
+ continue;
+ }
+
+ // Register the match.
+ UserMixup |= ConvertingResult.Flag;
+ Seq = ConvertingResult.ConvLTR;
+ }
+ }
+
+ if (const auto *LRT = LT->getAs<RecordType>()) {
+ LLVM_DEBUG(llvm::dbgs() << "Left hand side is a record type.\n");
+ const auto *RD = LRT->getAsCXXRecordDecl();
+ if (!RD)
+ // Initialisation of things from an unrelated C-style record type is not
+ // possible.
+ return MIXUP_None;
+ if (!RD->isCompleteDefinition())
+ // Against getVisibleConversionFunctions() assert.
+ return MIXUP_None;
+
+ for (const NamedDecl *Method : RD->getVisibleConversionFunctions()) {
+ LLVM_DEBUG(llvm::dbgs() << "Found visible conv operator: \n";
+ Method->dump(llvm::dbgs()); llvm::dbgs() << '\n';);
+
+ const auto *Con = dyn_cast<CXXConversionDecl>(Method);
+ if (!Con || Con->isExplicit())
+ continue;
+ LLVM_DEBUG(llvm::dbgs() << "Found a conversion method? \n";
+ Con->dump(llvm::dbgs()); llvm::dbgs() << '\n');
+
+ MixupData ConvertingResult = TryConversionOperator(Con, RT, LOpts);
+ if (ConvertingResult.Flag == MIXUP_None)
+ continue;
+
+ LLVM_DEBUG(llvm::dbgs() << "Came back with a positive result.\n");
+ FoundConversionOperator = true;
+
+ if (!ConvertingResult.ConvLTR.StdPost) {
+ // A match without a pre-conversion was found for the converting oper.
+ LLVM_DEBUG(llvm::dbgs() << "! EXACT MATCH.\n");
+ UserMixup |= ConvertingResult.Flag;
+ Seq = ConvertingResult.ConvLTR;
+ FoundMultipleMatches = false;
+ break;
+ }
+
+ if (!FoundMultipleMatches && Seq) {
+ // If the loop executes multiple times, it's possible to find multiple
+ // conversion operators with different, ambiguous conversion
+ // sequences coming from them. This is an error, but we need to check
+ // all constructors first for a possible exact match.
+ FoundMultipleMatches = true;
+ continue;
+ }
+
+ // Register the match.
+ UserMixup |= ConvertingResult.Flag;
+ Seq = ConvertingResult.ConvLTR;
+ }
+ }
+
+ if (FoundMultipleMatches) {
+ LLVM_DEBUG(llvm::dbgs() << "Multiple conversion sequences found! :(\n");
+ return MIXUP_None;
+ }
+ if (FoundConvertingCtor && FoundConversionOperator) {
+ LLVM_DEBUG(llvm::dbgs() << "Left<->Right ambiguous as both can be "
+ "converted to each other...\n");
+ return MIXUP_None;
+ }
+
+ LLVM_DEBUG(llvm::dbgs() << "Flags at the end of 'howConvertible?': "
+ << format_bytes(UserMixup) << '\n');
+ return Seq ? MixupData{UserMixup, Seq} : MixupData{MIXUP_None};
}
/// Gets the mixable range of the parameters of F starting with the param at
@@ -292,8 +776,7 @@
MixRange.NumParamsChecked = 1;
// Try checking parameters of the function from StartIdx until the range
- // breaks. The range contains parameters that are mutually mixable with each
- // other.
+ // breaks.
for (unsigned int I = StartIdx + 1; I < ParamCount; ++I) {
const ParmVarDecl *Ith = F->getParamDecl(I);
LLVM_DEBUG(llvm::dbgs() << "Check " << I << "th param:\n";
@@ -311,16 +794,17 @@
Mixup M{Jth, Ith,
HowPossibleToMixUpAtCallSite(Jth->getType(), Ith->getType(), Ctx,
- Checker.CVRMixPossible)};
+ Checker.CVRMixPossible,
+ Checker.ImplicitConversion)};
LLVM_DEBUG(llvm::dbgs() << "\tMixup (before sanitise)? "
- << llvm::format_bytes(M.Flags) << "\n";);
- M.sanitise();
+ << llvm::format_bytes(M.Data.Flag) << "\n";);
+ M.Data.sanitise();
LLVM_DEBUG(llvm::dbgs() << "\tMixup (after sanitise)? "
- << llvm::format_bytes(M.Flags) << "\n";);
- assert(M.Flags != MIXUP_Invalid &&
+ << llvm::format_bytes(M.Data.Flag) << "\n";);
+ assert(M.Data.Flag != MIXUP_Invalid &&
"Bits fell off, result is sentinel value.");
- if (M.Flags != MIXUP_None) {
+ if (M.Data.Flag != MIXUP_None) {
MixRange.Mixups.emplace_back(M);
AnyMixupStored = true;
}
@@ -532,7 +1016,8 @@
Options.get("IgnoredNames", DefaultIgnoredParamNames))),
IgnoredParamTypes(utils::options::parseStringList(
Options.get("IgnoredTypes", DefaultIgnoredParamTypes))),
- CVRMixPossible(Options.get("CVRMixPossible", false)) {}
+ CVRMixPossible(Options.get("CVRMixPossible", false)),
+ ImplicitConversion(Options.get("ImplicitConversion", false)) {}
void CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
@@ -542,6 +1027,7 @@
Options.store(Opts, "IgnoredTypes",
utils::options::serializeStringList(IgnoredParamTypes));
Options.store(Opts, "CVRMixPossible", CVRMixPossible);
+ Options.store(Opts, "ImplicitConversion", ImplicitConversion);
}
void CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck::
@@ -571,7 +1057,13 @@
/// Returns whether the given Mixup, when diagnosed, should elaborate the type
/// of the arguments involved.
static bool NeedsToPrintType(const Mixup &M) {
- return M.Flags & (MIXUP_Typedef | MIXUP_RefBind | MIXUP_CVR);
+ return M.Data.Flag & (MIXUP_Typedef | MIXUP_RefBind | MIXUP_CVR);
+}
+
+/// Returns whether the given Mixup, when diagnosed, should elaborate on
+/// implicit conversions.
+static bool NeedsToElaborateImplicitConversion(const Mixup &M) {
+ return M.Data.Flag & MIXUP_Implicit;
}
void CppcoreguidelinesAvoidAdjacentParametersOfTheSameTypeCheck::check(
@@ -609,6 +1101,8 @@
MixingRange.getFirstParm()->getType().getAsString(PP);
bool HasAnyTypePrint = llvm::any_of(MixingRange.Mixups, NeedsToPrintType);
+ bool HasAnyImplicits =
+ llvm::any_of(MixingRange.Mixups, NeedsToElaborateImplicitConversion);
{
const ParmVarDecl *RangeFirst = MixingRange.getFirstParm();
@@ -616,7 +1110,12 @@
{
StringRef MainDiagnostic;
- if (HasAnyTypePrint)
+ if (HasAnyImplicits)
+ MainDiagnostic =
+ "%0 adjacent parameters for '%1' of convertible types "
+ "may be easily swapped "
+ "by mistake";
+ else if (HasAnyTypePrint)
MainDiagnostic = "%0 adjacent parameters for '%1' of similar type "
"are easily swapped "
"by mistake";
@@ -628,7 +1127,7 @@
auto Diag = diag(RangeFirst->getOuterLocStart(), MainDiagnostic)
<< static_cast<unsigned>(MixingRange.NumParamsChecked)
<< FunName;
- if (!HasAnyTypePrint)
+ if (!HasAnyImplicits && !HasAnyTypePrint)
Diag << MainParmTypeAsWritten;
}
@@ -647,11 +1146,11 @@
llvm::SmallPtrSet<const ParmVarDecl *, 4> TypedefResolutionPrintedForParm;
llvm::SmallPtrSet<const ParmVarDecl *, 4> CVRNotePrintedForParm;
for (const Mixup &M : MixingRange.Mixups) {
- assert(M.Flags >= MIXUP_Trivial && "Too low bits in mixup type.");
+ assert(M.Data.Flag >= MIXUP_Trivial && "Too low bits in mixup type.");
// For MIXUP_Trivial no extra diagnostics required.
std::string FirstParmType, SecondParmType;
- if (NeedsToPrintType(M)) {
+ if (NeedsToPrintType(M) || NeedsToElaborateImplicitConversion(M)) {
// Typedefs, and reference binds might result in the type of a variable
// printed in the diagnostic, so we have to prepare it.
LLVM_DEBUG(llvm::dbgs()
@@ -663,7 +1162,7 @@
}
if (NeedsToPrintType(M)) {
- if (M.Flags & MIXUP_Typedef) {
+ if (M.Data.Flag & MIXUP_Typedef) {
// FIXME: Don't emit the typedef note for the parameter that isn't
// actually a typedef.
if (!TypedefResolutionPrintedForParm.count(M.First)) {
@@ -683,7 +1182,7 @@
}
}
- if (M.Flags & (MIXUP_RefBind | MIXUP_CVR)) {
+ if (M.Data.Flag & (MIXUP_RefBind | MIXUP_CVR)) {
if (!CVRNotePrintedForParm.count(M.Second)) {
diag(M.Second->getOuterLocStart(),
"at a call site, '%0' might bind with same force as '%1'",
@@ -693,6 +1192,55 @@
}
}
}
+
+ if (NeedsToElaborateImplicitConversion(M)) {
+ // FIXME: This seems to be VERY VERBOSE in some cases...
+ const ConversionSequence <R = M.Data.ConvLTR, &RTL = M.Data.ConvRTL;
+ const auto DiagnoseSequence =
+ [&M, &PP](const std::string &StartType,
+ const ConversionSequence &Seq) -> std::string {
+ assert((&Seq == &M.Data.ConvLTR || &Seq == &M.Data.ConvRTL) &&
+ "sequence should be a sequence from the capture.");
+ llvm::SmallString<256> SS;
+ llvm::raw_svector_ostream OS{SS};
+
+ OS << '\'' << StartType << '\'';
+ if (Seq.StdPre)
+ OS << " -> '" << TypeNameAsString(QualType{Seq.StdPre, 0}, PP)
+ << '\'';
+ if (Seq.UserType)
+ OS << " -> '" << TypeNameAsString(QualType{Seq.UserType, 0}, PP)
+ << '\'';
+ if (Seq.StdPost)
+ OS << " -> '" << TypeNameAsString(QualType{Seq.StdPost, 0}, PP)
+ << '\'';
+
+ return OS.str().str();
+ };
+
+ if (LTR && RTL) {
+ std::string ConvMsg =
+ "'%0' and '%1' can suffer implicit conversions between one "
+ "another";
+ if (!LTR.single() && !RTL.single())
+ ConvMsg.append(": %2 and %3");
+ else if (!LTR.single())
+ ConvMsg.append(": %2 and trivially");
+ else if (!RTL.single())
+ ConvMsg.append(": trivially and %2");
+
+ auto Diag =
+ diag(M.Second->getOuterLocStart(), ConvMsg, DiagnosticIDs::Note)
+ << FirstParmType << SecondParmType;
+ if (!LTR.single() && !RTL.single())
+ Diag << DiagnoseSequence(FirstParmType, LTR)
+ << DiagnoseSequence(SecondParmType, RTL);
+ else if (!LTR.single())
+ Diag << DiagnoseSequence(FirstParmType, LTR);
+ else if (!RTL.single())
+ Diag << DiagnoseSequence(SecondParmType, RTL);
+ }
+ }
}
}
}
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits