Skip to content

Commit a6f54c4

Browse files
authored
Merge branch 'main' into knewbury01/Declarations3
2 parents d9da42b + 5053722 commit a6f54c4

17 files changed

+501
-3
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/** Provides a library for errno-setting functions. */
2+
3+
import cpp
4+
5+
/*
6+
* An errno-setting function
7+
*/
8+
9+
abstract class ErrnoSettingFunction extends Function { }
10+
11+
/*
12+
* An errno-setting function that return out-of-band errors indicators
13+
*/
14+
15+
class OutOfBandErrnoSettingFunction extends ErrnoSettingFunction {
16+
OutOfBandErrnoSettingFunction() {
17+
this.hasGlobalName(["ftell", "fgetpos", "fsetpos", "mbrtowc", "wcrtomb", "wcsrtombs"])
18+
}
19+
}
20+
21+
/*
22+
* An errno-setting function that return in-band errors indicators
23+
*/
24+
25+
class InBandErrnoSettingFunction extends ErrnoSettingFunction {
26+
InBandErrnoSettingFunction() {
27+
this.hasGlobalName([
28+
"fgetwc", "fputwc", "strtol", "wcstol", "strtoll", "wcstoll", "strtoul", "wcstoul",
29+
"strtoull", "wcstoull", "strtoumax", "wcstoumax", "strtod", "wcstod", "strtof", "wcstof",
30+
"strtold", "wcstold", "strtoimax", "wcstoimax"
31+
])
32+
}
33+
}
34+
35+
/*
36+
* A assignment expression setting `errno` to 0
37+
*/
38+
39+
class ErrnoZeroed extends AssignExpr {
40+
ErrnoZeroed() {
41+
this.getLValue() = any(MacroInvocation ma | ma.getMacroName() = "errno").getExpr() and
42+
this.getRValue().getValue() = "0"
43+
}
44+
}
45+
46+
/*
47+
* A guard controlled by a errno comparison
48+
*/
49+
50+
abstract class ErrnoGuard extends StmtParent {
51+
abstract ControlFlowNode getZeroedSuccessor();
52+
53+
abstract ControlFlowNode getNonZeroedSuccessor();
54+
}
55+
56+
class ErrnoIfGuard extends EqualityOperation, ErrnoGuard {
57+
ControlStructure i;
58+
59+
ErrnoIfGuard() {
60+
this.getAnOperand() = any(MacroInvocation ma | ma.getMacroName() = "errno").getExpr() and
61+
this.getAnOperand().getValue() = "0" and
62+
i.getControllingExpr() = this
63+
}
64+
65+
Stmt getThenSuccessor() {
66+
i.getControllingExpr() = this and
67+
(result = i.(IfStmt).getThen() or result = i.(Loop).getStmt())
68+
}
69+
70+
Stmt getElseSuccessor() {
71+
i.getControllingExpr() = this and
72+
(
73+
i.(IfStmt).hasElse() and result = i.(IfStmt).getElse()
74+
or
75+
result = i.getFollowingStmt()
76+
)
77+
}
78+
79+
override ControlFlowNode getZeroedSuccessor() {
80+
if this instanceof EQExpr then result = this.getThenSuccessor() else result = getElseSuccessor()
81+
}
82+
83+
override ControlFlowNode getNonZeroedSuccessor() {
84+
if this instanceof NEExpr then result = this.getThenSuccessor() else result = getElseSuccessor()
85+
}
86+
}
87+
88+
class ErrnoSwitchGuard extends SwitchCase, ErrnoGuard {
89+
ErrnoSwitchGuard() {
90+
this.getSwitchStmt().getExpr() = any(MacroInvocation ma | ma.getMacroName() = "errno").getExpr()
91+
}
92+
93+
override ControlFlowNode getZeroedSuccessor() {
94+
result = this.getAStmt() and this.getExpr().getValue() = "0"
95+
}
96+
97+
override ControlFlowNode getNonZeroedSuccessor() {
98+
result = this.getAStmt() and this.getExpr().getValue() != "0"
99+
}
100+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @id c/misra/only-test-errno-right-after-errno-setting-function
3+
* @name RULE-22-10: The value of errno shall only be tested when the last called function is errno-setting
4+
* @description The value of errno shall only be tested when the last function to be called was an
5+
* errno-setting-function. Testing the value in these conditions does not guarantee the
6+
* absence of an errors.
7+
* @kind problem
8+
* @precision high
9+
* @problem.severity warning
10+
* @tags external/misra/id/rule-22-10
11+
* correctness
12+
* external/misra/obligation/required
13+
*/
14+
15+
import cpp
16+
import codingstandards.c.misra
17+
import codingstandards.c.Errno
18+
19+
/*
20+
* A call to an `ErrnoSettingFunction`
21+
*/
22+
23+
class ErrnoSettingFunctionCall extends FunctionCall {
24+
ErrnoSettingFunctionCall() { this.getTarget() instanceof ErrnoSettingFunction }
25+
}
26+
27+
/*
28+
* A function call that is not part of the `errno` macro expansion
29+
*/
30+
31+
class MaySetErrnoCall extends FunctionCall {
32+
MaySetErrnoCall() {
33+
not inmacroexpansion(this, any(MacroInvocation ma | ma.getMacroName() = "errno"))
34+
}
35+
}
36+
37+
/*
38+
* CFG nodes preceding a `errno` test where `errno` is not set
39+
*/
40+
41+
ControlFlowNode notSetPriorToErrnoTest(EqualityOperation eg) {
42+
result = eg
43+
or
44+
exists(ControlFlowNode mid |
45+
result = mid.getAPredecessor() and
46+
mid = notSetPriorToErrnoTest(eg) and
47+
// stop recursion on an errno-setting function call
48+
not result = any(ErrnoSettingFunctionCall c)
49+
)
50+
}
51+
52+
from EqualityOperation eq, ControlFlowNode cause
53+
where
54+
not isExcluded(eq, Contracts3Package::onlyTestErrnoRightAfterErrnoSettingFunctionQuery()) and
55+
cause = notSetPriorToErrnoTest(eq) and
56+
eq.getAnOperand() = any(MacroInvocation ma | ma.getMacroName() = "errno").getExpr() and
57+
(
58+
// `errno` is not set anywhere in the function
59+
cause = eq.getEnclosingFunction().getBlock()
60+
or
61+
// `errno` is not set after a non-errno-setting function call
62+
cause = any(MaySetErrnoCall c)
63+
)
64+
select eq, "Value of `errno` is tested but not after an errno-setting function call."
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* @id c/misra/errno-set-to-zero-prior-to-call
3+
* @name RULE-22-8: The value of errno shall be set to zero prior to a call to an errno-setting-function
4+
* @description The value of errno shall be set to zero prior to a call to an
5+
* errno-setting-function. Not setting the value leads to incorrectly identifying
6+
* errors.
7+
* @kind problem
8+
* @precision very-high
9+
* @problem.severity error
10+
* @tags external/misra/id/rule-22-8
11+
* correctness
12+
* external/misra/obligation/required
13+
*/
14+
15+
import cpp
16+
import codingstandards.c.misra
17+
import codingstandards.c.Errno
18+
19+
/*
20+
* A call to an `ErrnoSettingFunction`
21+
*/
22+
23+
class ErrnoSettingFunctionCall extends FunctionCall {
24+
ErrnoSettingFunctionCall() { this.getTarget() instanceof ErrnoSettingFunction }
25+
}
26+
27+
/*
28+
* CFG nodes preceding a `ErrnoSettingFunctionCall`
29+
*/
30+
31+
ControlFlowNode notZeroedPriorToErrnoSet(ErrnoSettingFunctionCall fc) {
32+
result = fc
33+
or
34+
exists(ControlFlowNode mid |
35+
result = mid.getAPredecessor() and
36+
mid = notZeroedPriorToErrnoSet(fc) and
37+
// stop recursion when `errno` is set to zero
38+
not result instanceof ErrnoZeroed and
39+
not result = any(ErrnoGuard g).getZeroedSuccessor()
40+
)
41+
}
42+
43+
from ErrnoSettingFunctionCall fc, ControlFlowNode cause
44+
where
45+
not isExcluded(fc, Contracts3Package::errnoSetToZeroAfterCallQuery()) and
46+
cause = notZeroedPriorToErrnoSet(fc) and
47+
(
48+
// `errno` is not reset anywhere in the function
49+
cause = fc.getEnclosingFunction().getBlock()
50+
or
51+
// `errno` is not reset after a call to an errno-setting function
52+
cause = any(ErrnoSettingFunctionCall ec | ec != fc)
53+
or
54+
// `errno` is not reset after a call to a function
55+
cause = any(FunctionCall fc2 | fc2 != fc)
56+
or
57+
// `errno` value is known to be != 0
58+
cause = any(ErrnoGuard g).getNonZeroedSuccessor()
59+
)
60+
select fc, "The value of `errno` may be different than `0` when this function is called."
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @id c/misra/errno-set-to-zero-after-call
3+
* @name RULE-22-9: The value of errno shall be tested against zero after calling an errno-setting-function
4+
* @description The value of errno shall be tested against zero after calling an
5+
* errno-setting-function. Not testing the value leads to unidentified errors.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity error
9+
* @tags external/misra/id/rule-22-9
10+
* correctness
11+
* external/misra/obligation/required
12+
*/
13+
14+
import cpp
15+
import codingstandards.c.misra
16+
import codingstandards.c.Errno
17+
18+
/*
19+
* A call to an `ErrnoSettingFunction`
20+
*/
21+
22+
class InBandErrnoSettingFunctionCall extends FunctionCall {
23+
InBandErrnoSettingFunctionCall() { this.getTarget() instanceof InBandErrnoSettingFunction }
24+
}
25+
26+
/*
27+
* CFG nodes following a `ErrnoSettingFunctionCall`
28+
*/
29+
30+
ControlFlowNode notTestedAfterErrnoSet(InBandErrnoSettingFunctionCall fc) {
31+
result = fc
32+
or
33+
exists(ControlFlowNode mid |
34+
result = mid.getASuccessor() and
35+
mid = notTestedAfterErrnoSet(fc) and
36+
// stop recursion when `errno` is checked
37+
not result = any(ControlStructure i | i.getControllingExpr() instanceof ErrnoGuard)
38+
)
39+
}
40+
41+
from InBandErrnoSettingFunctionCall fc, ControlFlowNode cause
42+
where
43+
not isExcluded(fc, Contracts3Package::errnoSetToZeroAfterCallQuery()) and
44+
cause = notTestedAfterErrnoSet(fc) and
45+
(
46+
// `errno` is not checked anywhere in the function
47+
cause = fc.getEnclosingFunction()
48+
or
49+
// `errno` is not checked before a call to a function
50+
cause = any(FunctionCall fc2 | fc2 != fc)
51+
)
52+
select fc, "The value of `errno` is not tested against `0` after the call."
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| test.c:7:7:7:16 | ... == ... | Value of `errno` is tested but not after an errno-setting function call. |
2+
| test.c:16:7:16:21 | ... == ... | Value of `errno` is tested but not after an errno-setting function call. |
3+
| test.c:17:10:17:23 | ... == ... | Value of `errno` is tested but not after an errno-setting function call. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/RULE-22-10/OnlyTestErrnoRightAfterErrnoSettingFunction.ql

c/misra/test/rules/RULE-22-10/test.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <errno.h>
2+
#include <stdlib.h>
3+
4+
void f1(void) {
5+
errno = 0;
6+
int i = atoi("0"); // non errno-setting function
7+
if (0 == errno) { // NON_COMPLIANT
8+
}
9+
errno = 0;
10+
strtod("0", NULL); // errno-setting function
11+
if (0 == errno) { // COMPLIANT
12+
}
13+
}
14+
15+
void f2(void) {
16+
if (EAGAIN == errno // NON_COMPLIANT
17+
|| errno == EINTR) { // NON_COMPLIANT
18+
}
19+
}
20+
21+
void f3(void) {
22+
errno = 0;
23+
strtod("0", NULL);
24+
if (errno == EAGAIN || errno == EINTR) { // COMPLIANT
25+
}
26+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| test.c:5:3:5:8 | call to strtod | The value of `errno` may be different than `0` when this function is called. |
2+
| test.c:20:5:20:10 | call to strtod | The value of `errno` may be different than `0` when this function is called. |
3+
| test.c:24:5:24:10 | call to strtof | The value of `errno` may be different than `0` when this function is called. |
4+
| test.c:32:3:32:8 | call to strtod | The value of `errno` may be different than `0` when this function is called. |
5+
| test.c:41:5:41:10 | call to strtod | The value of `errno` may be different than `0` when this function is called. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/RULE-22-8/ErrnoSetToZeroPriorToCall.ql

c/misra/test/rules/RULE-22-8/test.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <errno.h>
2+
#include <stdlib.h>
3+
4+
void f1(void) {
5+
strtod("0", NULL); // NON_COMPLIANT
6+
if (0 == errno) {
7+
strtod("0", NULL); // COMPLIANT
8+
if (0 == errno) {
9+
}
10+
} else {
11+
errno = 0;
12+
strtod("0", NULL); // COMPLIANT
13+
if (0 == errno) {
14+
}
15+
}
16+
}
17+
18+
void f2(void) {
19+
if (0 != errno) {
20+
strtod("0", NULL); // NON_COMPLIANT
21+
} else {
22+
errno = 0;
23+
strtod("0", NULL); // COMPLIANT
24+
strtof("0", NULL); // NON_COMPLIANT
25+
}
26+
}
27+
28+
void f3_helper() {}
29+
void f3(void) {
30+
errno = 0;
31+
f3_helper();
32+
strtod("0", NULL); // NON_COMPLIANT
33+
}
34+
35+
void f4(void) {
36+
errno = 0;
37+
switch (errno) {
38+
case 0:
39+
strtod("0", NULL); // COMPLIANT
40+
case 1:
41+
strtod("0", NULL); // NON_COMPLIANT
42+
}
43+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| test.c:9:3:9:8 | call to strtod | The value of `errno` is not tested against `0` after the call. |
2+
| test.c:19:3:19:8 | call to strtod | The value of `errno` is not tested against `0` after the call. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/RULE-22-9/ErrnoSetToZeroAfterCall.ql

0 commit comments

Comments
 (0)