Skip to content

Commit dbc828a

Browse files
committed
UncheckedRangeDomainPoleErrors: add cases for missing domain checks, pole error checks and one unspecified result case
1 parent c90966a commit dbc828a

File tree

6 files changed

+158
-28
lines changed

6 files changed

+158
-28
lines changed

Diff for: change_notes/2024-04-23-fix-fp-193.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- `A0-4-4`,`FLP32-C` - `UncheckedRangeDomainPoleErrors.ql`:
2+
- Fixes #193. Adds missing cases for domain errors, an unspecified result case and pole error cases.

Diff for: cpp/common/src/codingstandards/cpp/rules/uncheckedrangedomainpoleerrors/UncheckedRangeDomainPoleErrors.qll

+90-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ predicate hasDomainError(FunctionCall fc, string description) {
4242
upperBound(fc.getArgument(0)) < 1.0 and
4343
description = "argument is less than 1"
4444
or
45+
//pole error is the same as domain for logb and tgamma (but not ilogb - no pole error exists)
4546
functionWithDomainError = getMathVariants(["ilogb", "logb", "tgamma"]) and
4647
fc.getArgument(0).getValue().toFloat() = 0 and
4748
description = "argument is equal to zero"
@@ -53,18 +54,95 @@ predicate hasDomainError(FunctionCall fc, string description) {
5354
functionWithDomainError = getMathVariants("log1p") and
5455
upperBound(fc.getArgument(0)) < -1.0 and
5556
description = "argument is less than 1"
57+
or
58+
functionWithDomainError = getMathVariants("fmod") and
59+
fc.getArgument(1).getValue().toFloat() = 0 and
60+
description = "y is 0"
5661
)
5762
}
5863

64+
predicate hasRangeError(FunctionCall fc, string description) {
65+
exists(Function functionWithRangeError | fc.getTarget() = functionWithRangeError |
66+
functionWithRangeError.hasGlobalOrStdName(["abs", "labs", "llabs", "imaxabs"]) and
67+
fc.getArgument(0) = any(MINMacro m).getAnInvocation().getExpr() and
68+
description = "argument is most negative number"
69+
)
70+
}
71+
72+
predicate hasPoleError(FunctionCall fc, string description) {
73+
exists(Function functionWithPoleError | fc.getTarget() = functionWithPoleError |
74+
functionWithPoleError = getMathVariants("atanh") and
75+
(
76+
fc.getArgument(0).getValue().toFloat() = -1.0
77+
or
78+
fc.getArgument(0).getValue().toFloat() = 1.0
79+
) and
80+
description = "argument is plus or minus 1"
81+
or
82+
functionWithPoleError = getMathVariants("log1p") and
83+
fc.getArgument(0).getValue().toFloat() = -1 and
84+
description = "argument is equal to negative one"
85+
or
86+
functionWithPoleError = getMathVariants("pow") and
87+
fc.getArgument(0).getValue().toFloat() = 0.0 and
88+
fc.getArgument(1).getValue().toFloat() < 0.0 and
89+
description = "base is zero and exp is negative"
90+
or
91+
functionWithPoleError = getMathVariants("lgamma") and
92+
fc.getArgument(0).getValue().toFloat() = 0 and
93+
description = "argument is equal to zero"
94+
or
95+
functionWithPoleError = getMathVariants(["log", "log10", "log2"]) and
96+
fc.getArgument(0).getValue().toFloat() = 0.0 and
97+
description = "argument is equal to zero"
98+
)
99+
}
100+
101+
predicate unspecifiedValueCases(FunctionCall fc, string description) {
102+
exists(Function functionWithUnspecifiedResultError |
103+
fc.getTarget() = functionWithUnspecifiedResultError
104+
|
105+
functionWithUnspecifiedResultError = getMathVariants("frexp") and
106+
(
107+
fc.getArgument(0) = any(InfinityMacro m).getAnInvocation().getExpr() or
108+
fc.getArgument(0) = any(NanMacro m).getAnInvocation().getExpr()
109+
) and
110+
description = "Arg is Nan or infinity and exp is unspecified as a result"
111+
)
112+
}
113+
114+
/**
115+
* A macro which is representing infinity
116+
*/
117+
class InfinityMacro extends Macro {
118+
InfinityMacro() { this.getName().toLowerCase().matches("infinity") }
119+
}
120+
121+
/**
122+
* A macro which is representing nan
123+
*/
124+
class NanMacro extends Macro {
125+
NanMacro() { this.getName().toLowerCase().matches("nan") }
126+
}
127+
128+
/**
129+
* A macro which is representing INT_MIN or LONG_MIN or LLONG_MIN
130+
*/
131+
class MINMacro extends Macro {
132+
MINMacro() { this.getName().toLowerCase().matches(["int_min", "long_min", "llong_min"]) }
133+
}
134+
59135
/*
60136
* Domain cases not covered by this query:
61137
* - pow - x is finite and negative and y is finite and not an integer value.
62138
* - tgamma - negative integer can't be covered.
63139
* - lrint/llrint/lround/llround - no domain errors checked
64-
* - fmod - no domain errors checked.
65140
* - remainder - no domain errors checked.
66141
* - remquo - no domain errors checked.
67142
*
143+
* Pole cases not covered by this query:
144+
* - lgamma - negative integer can't be covered.
145+
*
68146
* Implementations may also define their own domain errors (as per the C99 standard), which are not
69147
* covered by this query.
70148
*/
@@ -73,6 +151,16 @@ query predicate problems(FunctionCall fc, string message) {
73151
not isExcluded(fc, getQuery()) and
74152
exists(string description |
75153
hasDomainError(fc, description) and
76-
message = "Domain error in call to " + fc.getTarget().getName() + ": " + description + "."
154+
message = "Domain error in call to '" + fc.getTarget().getName() + "': " + description + "."
155+
or
156+
hasRangeError(fc, description) and
157+
message = "Range error in call to '" + fc.getTarget().getName() + "': " + description + "."
158+
or
159+
hasPoleError(fc, description) and
160+
message = "Pole error in call to '" + fc.getTarget().getName() + "': " + description + "."
161+
or
162+
unspecifiedValueCases(fc, description) and
163+
message =
164+
"Unspecified error in call to '" + fc.getTarget().getName() + "': " + description + "."
77165
)
78166
}

Diff for: cpp/common/test/includes/standard-library/limits.h

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#define LLONG_MAX 9223372036854775807
2424
#define ULLONG_MIN 0ULL
2525
#define ULLONG_MAX 0xffffffffffffffff
26+
#define NAN (0.0f / 0.0f)
27+
#define INFINITY 1e5000f
2628

2729
namespace std {
2830
template <class T> class numeric_limits;

Diff for: cpp/common/test/includes/standard-library/math.h

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#ifndef _GHLIBCPP_MATH
22
#define _GHLIBCPP_MATH
3+
int abs(int);
4+
long abs(long);
35
double acos(double x);
46
float acosf(float x);
57
long double acosl(long double x);
@@ -15,9 +17,18 @@ long double acoshl(long double x);
1517
double atanh(double x);
1618
float atanhf(float x);
1719
long double atanhl(long double x);
20+
double fmod(double, double);
21+
float fmodf(float, float);
22+
long double fmodl(long double, long double);
23+
double frexp(double, int *);
24+
float frexpf(float, int *);
25+
long double frexpl(long double, int *);
1826
int ilogb(double x);
1927
int ilogbf(float x);
2028
int ilogbl(long double x);
29+
double lgamma(double);
30+
float lgammaf(float);
31+
long double lgammal(long double);
2132
double log(double x);
2233
float logf(float x);
2334
long double logl(long double x);
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
1-
| test.cpp:4:3:4:6 | call to acos | Domain error in call to acos: the argument has a range -1.1...-1.1 which is outside the domain of this function (-1.0...1.0). |
2-
| test.cpp:8:3:8:6 | call to acos | Domain error in call to acos: the argument has a range 1.1...1.1 which is outside the domain of this function (-1.0...1.0). |
3-
| test.cpp:9:3:9:6 | call to asin | Domain error in call to asin: the argument has a range -1.1...-1.1 which is outside the domain of this function (-1.0...1.0). |
4-
| test.cpp:13:3:13:6 | call to asin | Domain error in call to asin: the argument has a range 1.1...1.1 which is outside the domain of this function (-1.0...1.0). |
5-
| test.cpp:14:3:14:7 | call to atanh | Domain error in call to atanh: the argument has a range -1.1...-1.1 which is outside the domain of this function (-1.0...1.0). |
6-
| test.cpp:18:3:18:7 | call to atanh | Domain error in call to atanh: the argument has a range 1.1...1.1 which is outside the domain of this function (-1.0...1.0). |
7-
| test.cpp:19:3:19:7 | call to atan2 | Domain error in call to atan2: both arguments are equal to zero. |
8-
| test.cpp:23:3:23:5 | call to pow | Domain error in call to pow: both arguments are equal to zero. |
9-
| test.cpp:27:3:27:5 | call to pow | Domain error in call to pow: both arguments are less than zero. |
10-
| test.cpp:33:3:33:7 | call to acosh | Domain error in call to acosh: argument is less than 1. |
11-
| test.cpp:34:3:34:7 | call to ilogb | Domain error in call to ilogb: argument is equal to zero. |
12-
| test.cpp:37:3:37:5 | call to log | Domain error in call to log: argument is negative. |
13-
| test.cpp:40:3:40:7 | call to log10 | Domain error in call to log10: argument is negative. |
14-
| test.cpp:43:3:43:6 | call to log2 | Domain error in call to log2: argument is negative. |
15-
| test.cpp:46:3:46:6 | call to sqrt | Domain error in call to sqrt: argument is negative. |
16-
| test.cpp:49:3:49:7 | call to log1p | Domain error in call to log1p: argument is less than 1. |
17-
| test.cpp:52:3:52:6 | call to logb | Domain error in call to logb: argument is equal to zero. |
18-
| test.cpp:55:3:55:8 | call to tgamma | Domain error in call to tgamma: argument is equal to zero. |
1+
| test.cpp:5:3:5:6 | call to acos | Domain error in call to 'acos': the argument has a range -1.1...-1.1 which is outside the domain of this function (-1.0...1.0). |
2+
| test.cpp:9:3:9:6 | call to acos | Domain error in call to 'acos': the argument has a range 1.1...1.1 which is outside the domain of this function (-1.0...1.0). |
3+
| test.cpp:10:3:10:6 | call to asin | Domain error in call to 'asin': the argument has a range -1.1...-1.1 which is outside the domain of this function (-1.0...1.0). |
4+
| test.cpp:14:3:14:6 | call to asin | Domain error in call to 'asin': the argument has a range 1.1...1.1 which is outside the domain of this function (-1.0...1.0). |
5+
| test.cpp:15:3:15:7 | call to atanh | Domain error in call to 'atanh': the argument has a range -1.1...-1.1 which is outside the domain of this function (-1.0...1.0). |
6+
| test.cpp:17:3:17:7 | call to atanh | Domain error in call to 'atanh': the argument has a range 1.1...1.1 which is outside the domain of this function (-1.0...1.0). |
7+
| test.cpp:18:3:18:7 | call to atan2 | Domain error in call to 'atan2': both arguments are equal to zero. |
8+
| test.cpp:22:3:22:5 | call to pow | Domain error in call to 'pow': both arguments are equal to zero. |
9+
| test.cpp:26:3:26:5 | call to pow | Domain error in call to 'pow': both arguments are less than zero. |
10+
| test.cpp:31:3:31:7 | call to acosh | Domain error in call to 'acosh': argument is less than 1. |
11+
| test.cpp:32:3:32:7 | call to ilogb | Domain error in call to 'ilogb': argument is equal to zero. |
12+
| test.cpp:35:3:35:5 | call to log | Domain error in call to 'log': argument is negative. |
13+
| test.cpp:37:3:37:7 | call to log10 | Domain error in call to 'log10': argument is negative. |
14+
| test.cpp:39:3:39:6 | call to log2 | Domain error in call to 'log2': argument is negative. |
15+
| test.cpp:41:3:41:6 | call to sqrt | Domain error in call to 'sqrt': argument is negative. |
16+
| test.cpp:44:3:44:7 | call to log1p | Domain error in call to 'log1p': argument is less than 1. |
17+
| test.cpp:46:3:46:6 | call to logb | Domain error in call to 'logb': argument is equal to zero. |
18+
| test.cpp:49:3:49:8 | call to tgamma | Domain error in call to 'tgamma': argument is equal to zero. |
19+
| test.cpp:55:3:55:5 | call to abs | Range error in call to 'abs': argument is most negative number. |
20+
| test.cpp:56:3:56:6 | call to fmod | Domain error in call to 'fmod': y is 0. |
21+
| test.cpp:58:3:58:7 | call to frexp | Unspecified error in call to 'frexp': Arg is Nan or infinity and exp is unspecified as a result. |
22+
| test.cpp:59:3:59:7 | call to frexp | Unspecified error in call to 'frexp': Arg is Nan or infinity and exp is unspecified as a result. |
23+
| test.cpp:63:3:63:7 | call to atanh | Pole error in call to 'atanh': argument is plus or minus 1. |
24+
| test.cpp:64:3:64:7 | call to atanh | Pole error in call to 'atanh': argument is plus or minus 1. |
25+
| test.cpp:65:3:65:5 | call to log | Pole error in call to 'log': argument is equal to zero. |
26+
| test.cpp:66:3:66:7 | call to log10 | Pole error in call to 'log10': argument is equal to zero. |
27+
| test.cpp:67:3:67:6 | call to log2 | Pole error in call to 'log2': argument is equal to zero. |
28+
| test.cpp:68:3:68:7 | call to log1p | Pole error in call to 'log1p': argument is equal to negative one. |
29+
| test.cpp:70:3:70:5 | call to pow | Pole error in call to 'pow': base is zero and exp is negative. |
30+
| test.cpp:71:3:71:8 | call to lgamma | Pole error in call to 'lgamma': argument is equal to zero. |

Diff for: cpp/common/test/rules/uncheckedrangedomainpoleerrors/test.cpp

+23-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
#include "limits.h"
12
#include "math.h"
23

3-
void test() {
4+
void test_domain_errors() {
45
acos(-1.1f); // NON_COMPLIANT
56
acos(-1.0f); // COMPLIANT
67
acos(0.0f); // COMPLIANT
@@ -12,9 +13,7 @@ void test() {
1213
asin(1.0f); // COMPLIANT
1314
asin(1.1f); // NON_COMPLIANT
1415
atanh(-1.1f); // NON_COMPLIANT
15-
atanh(-1.0f); // COMPLIANT
1616
atanh(0.0f); // COMPLIANT
17-
atanh(1.0f); // COMPLIANT
1817
atanh(1.1f); // NON_COMPLIANT
1918
atan2(0.0f, 0.0f); // NON_COMPLIANT
2019
atan2(1.0f, 0.0f); // COMPLIANT
@@ -26,7 +25,6 @@ void test() {
2625
pow(1.0f, 1.0f); // COMPLIANT
2726
pow(-1.0f, -1.0f); // NON_COMPLIANT
2827
pow(-1.0f, 0.0f); // COMPLIANT
29-
pow(0.0f, -1.0f); // COMPLIANT
3028
pow(1.0f, -1.0f); // COMPLIANT
3129
pow(-1.0f, 1.0f); // COMPLIANT
3230
acosh(1.0f); // COMPLIANT
@@ -35,19 +33,15 @@ void test() {
3533
ilogb(1.0f); // COMPLIANT
3634
ilogb(-1.0f); // COMPLIANT
3735
log(-1.0f); // NON_COMPLIANT
38-
log(0.0f); // COMPLIANT
3936
log(1.0f); // COMPLIANT
4037
log10(-1.0f); // NON_COMPLIANT
41-
log10(0.0f); // COMPLIANT
4238
log10(1.0f); // COMPLIANT
4339
log2(-1.0f); // NON_COMPLIANT
44-
log2(0.0f); // COMPLIANT
4540
log2(1.0f); // COMPLIANT
4641
sqrt(-1.0f); // NON_COMPLIANT
4742
sqrt(0.0f); // COMPLIANT
4843
sqrt(1.0f); // COMPLIANT
4944
log1p(-2.0f); // NON_COMPLIANT
50-
log1p(-1.0f); // COMPLIANT
5145
log1p(0.0f); // COMPLIANT
5246
logb(0.0f); // NON_COMPLIANT
5347
logb(1.0f); // COMPLIANT
@@ -56,3 +50,24 @@ void test() {
5650
tgamma(1.0f); // COMPLIANT
5751
tgamma(-1.1f); // COMPLIANT
5852
}
53+
54+
void fn_in_193_missing_domain_or_range_cases() {
55+
abs(INT_MIN); // NON_COMPLIANT
56+
fmod(1.0f, 0.0f); // NON_COMPLIANT
57+
int *exp;
58+
frexp(NAN, exp); // NON_COMPLIANT
59+
frexp(INFINITY, exp); // NON_COMPLIANT
60+
}
61+
62+
void test_pole_errors() {
63+
atanh(-1.0f); // NON_COMPLIANT
64+
atanh(1.0f); // NON_COMPLIANT
65+
log(0.0f); // NON_COMPLIANT
66+
log10(0.0f); // NON_COMPLIANT
67+
log2(0.0f); // NON_COMPLIANT
68+
log1p(-1.0f); // NON_COMPLIANT
69+
// logb(x) already covered in domain cases
70+
pow(0.0f, -1.0f); // NON_COMPLIANT
71+
lgamma(0.0f); // NON_COMPLIANT
72+
lgamma(-1); // NON_COMPLIANT[FALSE_NEGATIVE]
73+
}

0 commit comments

Comments
 (0)