-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[CLANG] Add warning when INF or NAN are used in a binary operation or as function argument in fast math mode. #76873
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
Conversation
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.
Expanding the scope a bit, it would also be useful to have warnings for constant NaN or Inf values passed as arguments or used in binary operations.
clang/lib/Sema/SemaChecking.cpp
Outdated
@@ -2245,6 +2246,11 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, | |||
case Builtin::BI__builtin_islessequal: | |||
case Builtin::BI__builtin_islessgreater: | |||
case Builtin::BI__builtin_isunordered: | |||
if (BuiltinID == Builtin::BI__builtin_isunordered) { |
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.
Should this check be in SemaBuiltinUnorderedCompare()?
clang/lib/Sema/SemaChecking.cpp
Outdated
@@ -2267,6 +2273,16 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, | |||
case Builtin::BI__builtin_signbit: | |||
case Builtin::BI__builtin_signbitf: | |||
case Builtin::BI__builtin_signbitl: | |||
FPO = TheCall->getFPFeaturesInEffect(getLangOpts()); |
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.
Should these checks be in SemaBuiltinFPClassification()?
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.
Done.
clang/lib/Sema/SemaChecking.cpp
Outdated
/// Diagnose comparison to NAN or INFINITY in fast math modes. | ||
/// The comparison to NaN or INFINITY is always false in | ||
/// fast modes: float evaluation will not result in inf or nan. | ||
void Sema::CheckInfNaNFloatComparison(SourceLocation Loc, Expr *LHS, Expr *RHS, |
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.
This line is 81 characters long.
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.
It's 79 chars long.
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.
Oh, sorry. I must have copied the "+ " when I checked it. It just looked longer than the surrounding text.
clang/lib/Sema/SemaChecking.cpp
Outdated
bool NoHonorInfs = FPO.getNoHonorInfs(); | ||
llvm::APFloat Value(0.0); | ||
bool IsConstant; | ||
IsConstant = !LHS->isValueDependent() && |
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.
You could use a lambda here to avoid code duplication for LHS and RHS checking.
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.
Done.
clang/lib/Sema/SemaExpr.cpp
Outdated
if (Type->hasFloatingRepresentation()) | ||
if (Type->hasFloatingRepresentation()) { | ||
// Check for comparisons to NAN or INFINITY in fast math mode. | ||
S.CheckInfNaNFloatComparison(Loc, LHS.get(), RHS.get(), Opc); |
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.
Should this be done in CheckFloatComparison()?
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.
Done.
Added that. |
@llvm/pr-subscribers-clang Author: Zahira Ammarguellat (zahiraam) ChangesCheck for comparisons to INF and NaN when in ffast-math mode and generate a warning. Full diff: https://github.com/llvm/llvm-project/pull/76873.diff 5 Files Affected:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e54f969c19039d..d19be567a1bf66 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6771,6 +6771,9 @@ def warn_pointer_sub_null_ptr : Warning<
def warn_floatingpoint_eq : Warning<
"comparing floating point with == or != is unsafe">,
InGroup<DiagGroup<"float-equal">>, DefaultIgnore;
+def warn_fast_floatingpoint_eq : Warning<
+ "explicit comparison with %0 when the program is assumed to not use or produce %0">,
+ InGroup<TautologicalConstantCompare>;
def err_setting_eval_method_used_in_unsafe_context : Error <
"%select{'#pragma clang fp eval_method'|option 'ffp-eval-method'}0 cannot be used with "
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5e3b57ea33220b..b19ce6312e83bc 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13893,8 +13893,9 @@ class Sema final {
bool SemaBuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall);
bool SemaBuiltinVAStartARMMicrosoft(CallExpr *Call);
- bool SemaBuiltinUnorderedCompare(CallExpr *TheCall);
- bool SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs);
+ bool SemaBuiltinUnorderedCompare(CallExpr *TheCall, unsigned BuiltinID);
+ bool SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs,
+ unsigned BuiltinID);
bool SemaBuiltinComplex(CallExpr *TheCall);
bool SemaBuiltinVSX(CallExpr *TheCall);
bool SemaBuiltinOSLogFormat(CallExpr *TheCall);
@@ -13998,6 +13999,8 @@ class Sema final {
SourceRange range,
llvm::SmallBitVector &CheckedVarArgs);
+ void CheckInfNaNFunction(const CallExpr *Call, const FunctionDecl *FDecl);
+
void CheckAbsoluteValueFunction(const CallExpr *Call,
const FunctionDecl *FDecl);
@@ -14024,6 +14027,8 @@ class Sema final {
public:
void CheckFloatComparison(SourceLocation Loc, Expr *LHS, Expr *RHS,
BinaryOperatorKind Opcode);
+ void CheckInfNaNFloatComparison(SourceLocation Loc, Expr *LHS, Expr *RHS,
+ BinaryOperatorKind Opcode);
private:
void CheckImplicitConversions(Expr *E, SourceLocation CC = SourceLocation());
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 3168d38dd66c36..2c4d628ab160d2 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2169,6 +2169,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
ICEArguments &= ~(1 << ArgNo);
}
+ FPOptions FPO;
switch (BuiltinID) {
case Builtin::BI__builtin___CFStringMakeConstantString:
// CFStringMakeConstantString is currently not implemented for GOFF (i.e.,
@@ -2245,15 +2246,15 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
case Builtin::BI__builtin_islessequal:
case Builtin::BI__builtin_islessgreater:
case Builtin::BI__builtin_isunordered:
- if (SemaBuiltinUnorderedCompare(TheCall))
+ if (SemaBuiltinUnorderedCompare(TheCall, BuiltinID))
return ExprError();
break;
case Builtin::BI__builtin_fpclassify:
- if (SemaBuiltinFPClassification(TheCall, 6))
+ if (SemaBuiltinFPClassification(TheCall, 6, BuiltinID))
return ExprError();
break;
case Builtin::BI__builtin_isfpclass:
- if (SemaBuiltinFPClassification(TheCall, 2))
+ if (SemaBuiltinFPClassification(TheCall, 2, BuiltinID))
return ExprError();
break;
case Builtin::BI__builtin_isfinite:
@@ -2267,7 +2268,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
case Builtin::BI__builtin_signbit:
case Builtin::BI__builtin_signbitf:
case Builtin::BI__builtin_signbitl:
- if (SemaBuiltinFPClassification(TheCall, 1))
+ if (SemaBuiltinFPClassification(TheCall, 1, BuiltinID))
return ExprError();
break;
case Builtin::BI__builtin_shufflevector:
@@ -7621,6 +7622,7 @@ bool Sema::CheckFunctionCall(FunctionDecl *FDecl, CallExpr *TheCall,
CheckAbsoluteValueFunction(TheCall, FDecl);
CheckMaxUnsignedZero(TheCall, FDecl);
+ CheckInfNaNFunction(TheCall, FDecl);
if (getLangOpts().ObjC)
DiagnoseCStringFormatDirectiveInCFAPI(*this, FDecl, Args, NumArgs);
@@ -9090,10 +9092,16 @@ bool Sema::SemaBuiltinVAStartARMMicrosoft(CallExpr *Call) {
/// SemaBuiltinUnorderedCompare - Handle functions like __builtin_isgreater and
/// friends. This is declared to take (...), so we have to check everything.
-bool Sema::SemaBuiltinUnorderedCompare(CallExpr *TheCall) {
+bool Sema::SemaBuiltinUnorderedCompare(CallExpr *TheCall, unsigned BuiltinID) {
if (checkArgCount(*this, TheCall, 2))
return true;
+ if (BuiltinID == Builtin::BI__builtin_isunordered) {
+ if (TheCall->getFPFeaturesInEffect(getLangOpts()).getNoHonorNaNs())
+ Diag(TheCall->getBeginLoc(), diag::warn_fast_floatingpoint_eq)
+ << "NaN" << TheCall->getSourceRange();
+ }
+
ExprResult OrigArg0 = TheCall->getArg(0);
ExprResult OrigArg1 = TheCall->getArg(1);
@@ -9128,10 +9136,22 @@ bool Sema::SemaBuiltinUnorderedCompare(CallExpr *TheCall) {
/// SemaBuiltinSemaBuiltinFPClassification - Handle functions like
/// __builtin_isnan and friends. This is declared to take (...), so we have
/// to check everything.
-bool Sema::SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs) {
+bool Sema::SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs,
+ unsigned BuiltinID) {
if (checkArgCount(*this, TheCall, NumArgs))
return true;
+ FPOptions FPO = TheCall->getFPFeaturesInEffect(getLangOpts());
+ if (FPO.getNoHonorInfs() && (BuiltinID == Builtin::BI__builtin_isfinite ||
+ BuiltinID == Builtin::BI__builtin_isinf ||
+ BuiltinID == Builtin::BI__builtin_isinf_sign))
+ Diag(TheCall->getBeginLoc(), diag::warn_fast_floatingpoint_eq)
+ << "infinity" << TheCall->getSourceRange();
+ if (FPO.getNoHonorNaNs() && (BuiltinID == Builtin::BI__builtin_isnan ||
+ BuiltinID == Builtin::BI__builtin_isunordered))
+ Diag(TheCall->getBeginLoc(), diag::warn_fast_floatingpoint_eq)
+ << "NaN" << TheCall->getSourceRange();
+
bool IsFPClass = NumArgs == 2;
// Find out position of floating-point argument.
@@ -12878,6 +12898,22 @@ static bool IsStdFunction(const FunctionDecl *FDecl,
return true;
}
+void Sema::CheckInfNaNFunction(const CallExpr *Call,
+ const FunctionDecl *FDecl) {
+ FPOptions FPO = Call->getFPFeaturesInEffect(getLangOpts());
+ if ((IsStdFunction(FDecl, "isnan") || IsStdFunction(FDecl, "isunordered") ||
+ (Call->getBuiltinCallee() == Builtin::BI__builtin_nanf)) &&
+ FPO.getNoHonorNaNs())
+ Diag(Call->getBeginLoc(), diag::warn_fast_floatingpoint_eq)
+ << "NaN" << Call->getSourceRange();
+ else if ((IsStdFunction(FDecl, "isinf") ||
+ (IsStdFunction(FDecl, "isfinite") ||
+ (Call->getBuiltinCallee() == Builtin::BI__builtin_inff))) &&
+ FPO.getNoHonorInfs())
+ Diag(Call->getBeginLoc(), diag::warn_fast_floatingpoint_eq)
+ << "infinity" << Call->getSourceRange();
+}
+
// Warn when using the wrong abs() function.
void Sema::CheckAbsoluteValueFunction(const CallExpr *Call,
const FunctionDecl *FDecl) {
@@ -13846,6 +13882,38 @@ Sema::CheckReturnValExpr(Expr *RetValExp, QualType lhsType,
CheckPPCMMAType(RetValExp->getType(), ReturnLoc);
}
+/// Diagnose comparison to NAN or INFINITY in fast math modes.
+/// The comparison to NaN or INFINITY is always false in
+/// fast modes: float evaluation will not result in inf or nan.
+void Sema::CheckInfNaNFloatComparison(SourceLocation Loc, Expr *LHS, Expr *RHS,
+ BinaryOperatorKind Opcode) {
+ Expr *LeftExprSansParen = LHS->IgnoreParenImpCasts();
+ Expr *RightExprSansParen = RHS->IgnoreParenImpCasts();
+
+ FPOptions FPO = LHS->getFPFeaturesInEffect(getLangOpts());
+ bool NoHonorNaNs = FPO.getNoHonorNaNs();
+ bool NoHonorInfs = FPO.getNoHonorInfs();
+ llvm::APFloat Value(0.0);
+
+ auto IsConstant = [&Value](Expr *E, Expr *ESansParen, ASTContext &Context) {
+ return !E->isValueDependent() &&
+ ESansParen->EvaluateAsFloat(Value, Context,
+ Expr::SE_AllowSideEffects);
+ };
+
+ if (IsConstant(LHS, LeftExprSansParen, Context) &&
+ ((NoHonorNaNs && Value.isNaN()) || (NoHonorInfs && Value.isInfinity())))
+ Diag(Loc, diag::warn_fast_floatingpoint_eq)
+ << (Value.isNaN() ? "NaN" : "infinity") << LHS->getSourceRange()
+ << RHS->getSourceRange();
+
+ if (IsConstant(RHS, RightExprSansParen, Context) &&
+ ((NoHonorNaNs && Value.isNaN()) || (NoHonorInfs && Value.isInfinity())))
+ Diag(Loc, diag::warn_fast_floatingpoint_eq)
+ << (Value.isNaN() ? "NaN" : "infinity") << LHS->getSourceRange()
+ << RHS->getSourceRange();
+}
+
/// Check for comparisons of floating-point values using == and !=. Issue a
/// warning if the comparison is not likely to do what the programmer intended.
void Sema::CheckFloatComparison(SourceLocation Loc, Expr *LHS, Expr *RHS,
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 960f513d1111b2..005ddfa882195d 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -13044,9 +13044,12 @@ static QualType checkArithmeticOrEnumeralCompare(Sema &S, ExprResult &LHS,
if (Type->isAnyComplexType() && BinaryOperator::isRelationalOp(Opc))
return S.InvalidOperands(Loc, LHS, RHS);
- // Check for comparisons of floating point operands using != and ==.
- if (Type->hasFloatingRepresentation())
+ if (Type->hasFloatingRepresentation()) {
+ // Check for comparisons to NAN or INFINITY in fast math mode.
+ S.CheckInfNaNFloatComparison(Loc, LHS.get(), RHS.get(), Opc);
+ // Check for comparisons of floating point operands using != and ==.
S.CheckFloatComparison(Loc, LHS.get(), RHS.get(), Opc);
+ }
// The result of comparisons is 'bool' in C++, 'int' in C.
return S.Context.getLogicalOperationType();
diff --git a/clang/test/Sema/warn-fp-fast-compare.cpp b/clang/test/Sema/warn-fp-fast-compare.cpp
new file mode 100644
index 00000000000000..df8c8145f538ad
--- /dev/null
+++ b/clang/test/Sema/warn-fp-fast-compare.cpp
@@ -0,0 +1,245 @@
+// RUN: %clang_cc1 -x c++ -verify -triple powerpc64le-unknown-unknown %s \
+// RUN: -menable-no-infs -menable-no-nans -DFAST=1
+
+// RUN: %clang_cc1 -x c++ -verify -triple powerpc64le-unknown-unknown %s \
+// RUN: -DNOFAST=1
+
+// RUN: %clang_cc1 -x c++ -verify -triple powerpc64le-unknown-unknown %s \
+// RUN: -menable-no-infs -DNO_INFS=1
+
+// RUN: %clang_cc1 -x c++ -verify -triple powerpc64le-unknown-unknown %s \
+// RUN: -menable-no-nans -DNO_NANS=1
+
+int isunorderedf (float x, float y);
+#if NOFAST
+// expected-no-diagnostics
+#endif
+extern "C++" {
+namespace std __attribute__((__visibility__("default"))) {
+ bool
+ isinf(float __x);
+ bool
+ isinf(double __x);
+ bool
+ isinf(long double __x);
+ bool
+ isnan(float __x);
+ bool
+ isnan(double __x);
+ bool
+ isnan(long double __x);
+bool
+ isfinite(float __x);
+ bool
+ isfinite(double __x);
+ bool
+ isfinte(long double __x);
+ bool
+ isunordered(float __x, float __y);
+ bool
+ isunordered(double __x, double __y);
+ bool
+ isunordered(long double __x, long double __y);
+} // namespace )
+}
+#define NAN (__builtin_nanf(""))
+#define INFINITY (__builtin_inff())
+
+int compareit(float a, float b) {
+ volatile int i, j, k, l, m, n, o, p;
+#if FAST
+// expected-warning@+11 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ i = a == INFINITY;
+#if FAST
+// expected-warning@+11 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ j = INFINITY == a;
+#if FAST
+// expected-warning@+11 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+2 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+ i = a == NAN;
+#if FAST
+// expected-warning@+11 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+2 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+ j = NAN == a;
+#if FAST
+// expected-warning@+11 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ j = INFINITY <= a;
+#if FAST
+// expected-warning@+11 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+ // expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ j = INFINITY < a;
+#if FAST
+// expected-warning@+11 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+ // expected-warning@+2 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+ j = a > NAN;
+#if FAST
+// expected-warning@+11 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+2 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+ j = a >= NAN;
+#if FAST
+// expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+k = std::isinf(a);
+#if FAST
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+2 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+ l = std::isnan(a);
+#if FAST
+// expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ o = std::isfinite(a);
+#if FAST
+// expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ m = __builtin_isinf(a);
+#if FAST
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+2 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+ n = __builtin_isnan(a);
+#if FAST
+// expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ p = __builtin_isfinite(a);
+
+ // These should NOT warn, since they are not comparing with NaN or infinity.
+ j = a > 1.1;
+ j = b < 1.1;
+ j = a >= 1.1;
+ j = b <= 1.1;
+ j = isunorderedf(a, b);
+
+#if FAST
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+2 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+ j = isunorderedf(a, NAN);
+#if FAST
+// expected-warning@+5 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ j = isunorderedf(a, INFINITY);
+#if FAST
+// expected-warning@+11 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+2 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+ i = std::isunordered(a, NAN);
+#if FAST
+// expected-warning@+11 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+#if FAST
+// expected-warning@+8 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_NANS
+// expected-warning@+5 {{explicit comparison with NaN when the program is assumed to not use or produce NaN}}
+#endif
+#if NO_INFS
+// expected-warning@+2 {{explicit comparison with infinity when the program is assumed to not use or produce infinity}}
+#endif
+ i = std::isunordered(a, INFINITY);
+ return 0;
+}
|
} | ||
#define NAN (__builtin_nanf("")) | ||
#define INFINITY (__builtin_inff()) | ||
|
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.
What about through std::numeric_limits::infinity?
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.
Done.
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.
The changes also need a release note.
@@ -6771,6 +6771,9 @@ def warn_pointer_sub_null_ptr : Warning< | |||
def warn_floatingpoint_eq : Warning< | |||
"comparing floating point with == or != is unsafe">, | |||
InGroup<DiagGroup<"float-equal">>, DefaultIgnore; | |||
def warn_fast_floatingpoint_eq : Warning< | |||
"explicit comparison with %0 when the program is assumed to not use or produce %0">, |
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.
"explicit comparison with %0 when the program is assumed to not use or produce %0">, | |
"explicit comparison with %select{infinity|NaN}0 will always return '%select{false|true}1' because %select{infinity|NaN}0 will not be produced according to the currently enabled floating-point options">, |
How about something along these lines? I mostly want to replace the %0
with a select
so we don't have to pass in constant strings at the use sites, and I want to explicitly tell the user whether the comparison will always be true or always be false.
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.
Not sure this makes sense. Since I am also adding this warning to things like a * INF/NaN or foo(INF). May be "explicit comparison" should be replace with "use of"?
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.
Ah, good point! How about...
explicit comparison of an expression involving %select{an infinity|a NaN}0 constant will always return '%select{false|true}1' because %select{infinity|NaN}0 will not be produced according to the currently enabled floating-point options
? It's a bit wordy, so maybe "use of infinity|NaN will always" is okay as well.
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.
If it's ok I will use "use of infinity|NaN will always" only because the warning is not triggered only for comparison but for binary operators using inf/nan and function calls with inf/nan arguments.
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.
@AaronBallman WDYT?
clang/lib/Sema/SemaChecking.cpp
Outdated
if (BuiltinID == Builtin::BI__builtin_isunordered) { | ||
if (TheCall->getFPFeaturesInEffect(getLangOpts()).getNoHonorNaNs()) | ||
Diag(TheCall->getBeginLoc(), diag::warn_fast_floatingpoint_eq) | ||
<< "NaN" << TheCall->getSourceRange(); | ||
} |
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.
if (BuiltinID == Builtin::BI__builtin_isunordered) { | |
if (TheCall->getFPFeaturesInEffect(getLangOpts()).getNoHonorNaNs()) | |
Diag(TheCall->getBeginLoc(), diag::warn_fast_floatingpoint_eq) | |
<< "NaN" << TheCall->getSourceRange(); | |
} | |
if (BuiltinID == Builtin::BI__builtin_isunordered && | |
TheCall->getFPFeaturesInEffect(getLangOpts()).getNoHonorNaNs()) | |
Diag(TheCall->getBeginLoc(), diag::warn_fast_floatingpoint_eq) | |
<< "NaN" << TheCall->getSourceRange(); |
@@ -6771,6 +6771,11 @@ def warn_pointer_sub_null_ptr : Warning< | |||
def warn_floatingpoint_eq : Warning< | |||
"comparing floating point with == or != is unsafe">, | |||
InGroup<DiagGroup<"float-equal">>, DefaultIgnore; | |||
def warn_fast_floatingpoint_eq : Warning< | |||
"use of %select{infinity|NaN}0 will always be 'false' because " |
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.
This doesn't make sense to me. What does it mean for a use to be false?
Maybe we're trying to wrap too much into a single diagnostic. For comparisons, the result of the comparison will be constant, so it makes sense to have a warning that tells the user what that constant result will be. However, when a constant NaN or infinity is passed as an argument to a function call, the situation is a bit more complicated. In that case, the optimizer can treat the input as poison. What effect that will have depends on the content of the called function. It might do nothing. It might cause the entire function call to be erased.
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.
Yea! not happy with this either.
May be saying something like this to warn the user the existence of inf/nan use: "use of inf/nan when the program will not be producing inf/nan according to the currently enabled floating-point options"?
✅ With the latest revision this PR passed the C/C++ code formatter. |
"%select{explicit comparison with|use of}0 %select{infinity|nan}1 " | ||
"%select{will always return 'false'|as a function argument will not always be interpreted as such}0 " | ||
"because %select{infinity|nan}1 will not be produced according to the " | ||
"currently enabled floating-point options">, |
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.
Ah, now that I better understand the scope of the functionality, I think we should go with:
use of %select{infinity|NaN}0 results in undefined behavior due to the currently enabled floating-point options
and move the diagnostic out of the tautological compare warning group. You should also update ExprConstant.cpp to reject use of inf/nan in a constant expression with the options disabled.
clang/lib/Sema/SemaChecking.cpp
Outdated
/// Diagnose comparison to NAN or INFINITY in fast math modes. | ||
/// The comparison to NaN or INFINITY is always false in | ||
/// fast modes: float evaluation will not result in inf or nan. | ||
void Sema::CheckInfNaNFloatComparison(SourceLocation Loc, Expr *LHS, Expr *RHS, |
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.
The basic idea I have is that it's UB to generate a constant infinity or NaN if infinity/NaN is disabled. Instead of performing special logic for float comparisons, I think the logic should live in Sema::ActOnNumericConstant()
so that we capture any time a float or infinity constant is used. e.g. inf + 1
should also diagnose.
Then the tautological comparison component to this could be implemented by looking for calls to __builtin_isinf
or __builtin_isnan
and claim those are tautologically false (as a follow-up patch). WDYT?
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 have actually tried that but I noticed that getSpellingOfSingleCharacterNumericConstant
of inf/nan will both return "0" so I will have no way to distinguish between inf + 1 and 0 + 1 and both will generate a warning. Unless there is other way of finding out that the token is nan/inf?
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.
Why are we on that path in the first place? The token length shouldn't be one character long for either inf
or nan
right? I was thinking you should be able to call NumericLiteralParser::GetFloatValue()
within the } else if (Literal.isFloatingLiteral()) {
block to get an APFloat
that you can call isInfinity()
or isNaN()
on and then diagnose at that point.
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.
@AaronBallman Thanks.
when INFINITY/NAN macros are used and the other when __builtin_inf() and __builtin_nan() are used.
@@ -70,6 +70,10 @@ def warn_pragma_debug_missing_argument : Warning< | |||
def warn_pragma_debug_unexpected_argument : Warning< | |||
"unexpected argument to debug command">, InGroup<IgnoredPragmas>; | |||
|
|||
def warn_fp_nan_inf_when_disabled : Warning< | |||
"use of %select{infinity|NaN}0 %select{|via a macro}1 results in an undefined behavior " |
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.
"use of %select{infinity|NaN}0 %select{|via a macro}1 results in an undefined behavior " | |
"use of %select{infinity|NaN}0%select{| via a macro}1 results in undefined behavior " |
How about this?
@@ -2835,6 +2835,9 @@ class Preprocessor { | |||
if (Identifier.getIdentifierInfo()->isRestrictExpansion() && | |||
!SourceMgr.isInMainFile(Identifier.getLocation())) | |||
emitRestrictExpansionWarning(Identifier); | |||
|
|||
if (Identifier.getIdentifierInfo()->getName() == "INFINITY") |
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.
What about NAN
?
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.
Since NAN is defined as (-(float)(INFINITY * 0.0F)) when used in the program it will trigger the "use of infinity via a macro results in undefined behavior due to the currently enabled floating-point options [-Wnan-and-infinity-disabled]". Wouldn't that be enough? If I add NaN here we would get 2 warnings.
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.
Because there are other C standard libraries which do something different. For example, musl: https://git.musl-libc.org/cgit/musl/tree/include/math.h#n18
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.
Since NAN is defined as (-(float)(INFINITY * 0.0F)) when used in the program it will trigger the "use of infinity via a macro results in undefined behavior due to the currently enabled floating-point options [-Wnan-and-infinity-disabled]". Wouldn't that be enough? If I add NaN here we would get 2 warnings.
This is actually a bit unfortunate. If I use "-fhonor-nans -fno-honor-infinities" this definition of NAN will trigger a warning, even though I expect that we'll constant-fold it as expected without regard to the "no-honor-infinities" setting.
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.
We will?
#ifndef _HUGE_ENUF
#define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow
#endif
#define INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF))
...
#define NAN ((float)(INFINITY * 0.0F))
the multiplication for INFINITY
won't form an actual infinity value in that case, so why would the multiplication with 0.0
not generate a NAN macro that folds to 0.0F?
If use of INFINITY
is undefined behavior when -fno-honor-infinities
is passed... it makes sense to me that the definition of NAN
using INFINITY
is UB by the same logic.
def warn_fp_nan_inf_when_disabled : Warning< | ||
"use of %select{infinity|NaN}0%select{| via a macro}1 results in undefined behavior " | ||
"due to the currently enabled floating-point options">, | ||
InGroup<DiagGroup<"nan-and-infinity-disabled">>; |
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.
def warn_fp_nan_inf_when_disabled : Warning< | |
"use of %select{infinity|NaN}0%select{| via a macro}1 results in undefined behavior " | |
"due to the currently enabled floating-point options">, | |
InGroup<DiagGroup<"nan-and-infinity-disabled">>; | |
def warn_fp_nan_inf_when_disabled : Warning< | |
"use of %select{infinity|NaN}0%select{| via a macro}1 is undefined behavior " | |
"due to the currently enabled floating-point options">, | |
InGroup<DiagGroup<"nan-infinity-disabled">>; |
Sorry for still puttering around with what color to paint the bikeshed, but I think this might be slightly more concise wording and a somewhat better diagnostic group.
#if FAST | ||
// expected-warning@+5 {{use of infinity via a macro results in undefined behavior due to the currently enabled floating-point options}} | ||
#endif | ||
#if NO_INFS | ||
// expected-warning@+2 {{use of infinity via a macro results in undefined behavior due to the currently enabled floating-point options}} | ||
#endif |
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.
Rather than use the preprocessor to have conditional diagnostics, it's better to use diagnostics with different prefixes. e.g.,
// RUN: %clang_cc1 -x c++ -verify=no-inf,no-nan -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-infs -menable-no-nans
// RUN: %clang_cc1 -x c++ -verify=no-inf -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-infs
// RUN: %clang_cc1 -x c++ -verify=no-nan -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-nans
...
i = a == INFINITY; // no-inf-warning {{blah blah blah}}
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.
The changes LGTM aside from some minor improvements to the tests.
// RUN: %clang_cc1 -x c++ -verify -triple powerpc64le-unknown-unknown %s \ | ||
// RUN: -DNOFAST=1 | ||
|
||
// RUN: %clang_cc1 -x c++ -verify=no-inf -triple powerpc64le-unknown-unknown %s \ | ||
// RUN: -menable-no-infs | ||
|
||
// RUN: %clang_cc1 -x c++ -verify=no-nan -triple powerpc64le-unknown-unknown %s \ | ||
// RUN: -menable-no-nans | ||
|
||
int isunorderedf (float x, float y); | ||
#if NOFAST | ||
// expected-no-diagnostics | ||
#endif |
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.
// RUN: %clang_cc1 -x c++ -verify -triple powerpc64le-unknown-unknown %s \ | |
// RUN: -DNOFAST=1 | |
// RUN: %clang_cc1 -x c++ -verify=no-inf -triple powerpc64le-unknown-unknown %s \ | |
// RUN: -menable-no-infs | |
// RUN: %clang_cc1 -x c++ -verify=no-nan -triple powerpc64le-unknown-unknown %s \ | |
// RUN: -menable-no-nans | |
int isunorderedf (float x, float y); | |
#if NOFAST | |
// expected-no-diagnostics | |
#endif | |
// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown %s | |
// RUN: %clang_cc1 -x c++ -verify=no-inf -triple powerpc64le-unknown-unknown %s \ | |
// RUN: -menable-no-infs | |
// RUN: %clang_cc1 -x c++ -verify=no-nan -triple powerpc64le-unknown-unknown %s \ | |
// RUN: -menable-no-nans | |
// no-fast-no-diagnostics | |
int isunorderedf (float x, float y); |
|
||
int isunorderedf (float x, float y); | ||
#if NOFAST | ||
// expected-no-diagnostics |
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.
Same thing here as with the other test.
Any other comments from @arsenm and @andykaylor? |
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'm happy with this.
Hello, It seems like with this change it warns like
with
on input like
Is this expected and wanted? |
Good catch! I would not expect that diagnostic; we should silence the diagnostic if it's used in a |
Thanks @mikaelholmen and @AaronBallman. I will take a look and create a PR to fix the issue. |
Hi @zahiraam ! |
Sorry! I didn't have time to work on it yet. I should probably get to it next week. |
Hi, I am seeing breakages on some of our audio code on tip of tree Clang and I traced it back to this patch. We uses Thanks. |
I think this must be related to the comment above. I am in process of creating a PR to fix it. But if it can't wait I will revert this. |
What is the order you're passing the flags? This may have actually pointed out a difference between what you expected and what actually happens (which is part of the point to these changes): https://godbolt.org/z/x54qTGoMG |
Because these changes made it into 18.x and we're already seeing feedback about the diagnostic quality, I think we need to either revert the changes for the moment (both on main and on 18.x) or we need to commit to getting the fix as quickly as possible (and backport it to 18.x). I'd like to avoid getting into a situation where we're reverting this on 18.x later in the cycle. How about this for a plan: if there's not a fix committed by EOD Mon (2/5), we revert so that rc2 goes out in a good state (rc2 is being cut on Tue the 6th)? |
Thanks for the tip. |
In #76873 a warning was added when the macros INFINITY and NAN are used in binary expressions when -menable-no-nans or -menable-no-infs are used. If the user uses an option that nullifies these two options, the warning will still be generated. This patch adds an additional information to the warning comment to let the user know about this. It also suppresses the warning when #ifdef INFINITY, #ifdef NAN, #ifdef NAN or #ifndef NAN are used in the code.
In llvm#76873 a warning was added when the macros INFINITY and NAN are used in binary expressions when -menable-no-nans or -menable-no-infs are used. If the user uses an option that nullifies these two options, the warning will still be generated. This patch adds an additional information to the warning comment to let the user know about this. It also suppresses the warning when #ifdef INFINITY, #ifdef NAN, #ifdef NAN or #ifndef NAN are used in the code. (cherry picked from commit 62c352e)
In llvm#76873 a warning was added when the macros INFINITY and NAN are used in binary expressions when -menable-no-nans or -menable-no-infs are used. If the user uses an option that nullifies these two options, the warning will still be generated. This patch adds an additional information to the warning comment to let the user know about this. It also suppresses the warning when #ifdef INFINITY, #ifdef NAN, #ifdef NAN or #ifndef NAN are used in the code. (cherry picked from commit 62c352e)
In llvm#76873 a warning was added when the macros INFINITY and NAN are used in binary expressions when -menable-no-nans or -menable-no-infs are used. If the user uses an option that nullifies these two options, the warning will still be generated. This patch adds an additional information to the warning comment to let the user know about this. It also suppresses the warning when #ifdef INFINITY, #ifdef NAN, #ifdef NAN or #ifndef NAN are used in the code. (cherry picked from commit 62c352e)
In llvm#76873 a warning was added when the macros INFINITY and NAN are used in binary expressions when -menable-no-nans or -menable-no-infs are used. If the user uses an option that nullifies these two options, the warning will still be generated. This patch adds an additional information to the warning comment to let the user know about this. It also suppresses the warning when #ifdef INFINITY, #ifdef NAN, #ifdef NAN or #ifndef NAN are used in the code. (cherry picked from commit 62c352e)
In llvm#76873 a warning was added when the macros INFINITY and NAN are used in binary expressions when -menable-no-nans or -menable-no-infs are used. If the user uses an option that nullifies these two options, the warning will still be generated. This patch adds an additional information to the warning comment to let the user know about this. It also suppresses the warning when #ifdef INFINITY, #ifdef NAN, #ifdef NAN or #ifndef NAN are used in the code. (cherry picked from commit 62c352e)
In llvm#76873 a warning was added when the macros INFINITY and NAN are used in binary expressions when -menable-no-nans or -menable-no-infs are used. If the user uses an option that nullifies these two options, the warning will still be generated. This patch adds an additional information to the warning comment to let the user know about this. It also suppresses the warning when #ifdef INFINITY, #ifdef NAN, #ifdef NAN or #ifndef NAN are used in the code. (cherry picked from commit 62c352e)
In llvm#76873 a warning was added when the macros INFINITY and NAN are used in binary expressions when -menable-no-nans or -menable-no-infs are used. If the user uses an option that nullifies these two options, the warning will still be generated. This patch adds an additional information to the warning comment to let the user know about this. It also suppresses the warning when #ifdef INFINITY, #ifdef NAN, #ifdef NAN or #ifndef NAN are used in the code. (cherry picked from commit 62c352e)
Check for operations using INF or NaN when in ffast-math mode and generate a warning.