Skip to content

Commit 4623f33

Browse files
First pass at addressing cross-compiler compatibility in MISRA 2023.
Handles clang findings and gcc findings. Many issues were merely updates to the test cases, however, additional work has been done to properly handle tgmath.h and stdatomic.h macros across gcc and clang results.
1 parent eedca61 commit 4623f33

File tree

41 files changed

+1698
-233
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1698
-233
lines changed

Diff for: c/common/src/codingstandards/c/TgMath.qll

+49-10
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
import cpp
22

3-
private string getATgMathMacroName(boolean allowComplex) {
3+
private string getATgMathMacroName(boolean allowComplex, int numberOfParameters) {
44
allowComplex = true and
5+
numberOfParameters = 1 and
56
result =
67
[
78
"acos", "acosh", "asin", "asinh", "atan", "atanh", "carg", "cimag", "conj", "cos", "cosh",
8-
"cproj", "creal", "exp", "fabs", "log", "pow", "sin", "sinh", "sqrt", "tan", "tanh"
9+
"cproj", "creal", "exp", "fabs", "log", "sin", "sinh", "sqrt", "tan", "tanh"
10+
]
11+
or
12+
allowComplex = true and
13+
numberOfParameters = 2 and
14+
result = "pow"
15+
or
16+
allowComplex = false and
17+
numberOfParameters = 1 and
18+
result =
19+
[
20+
"cbrt", "ceil", "erf", "erfc", "exp2", "expm1", "floor", "ilogb", "lgamma", "llrint",
21+
"llround", "log10", "log1p", "log2", "logb", "lrint", "lround", "nearbyint", "rint", "round",
22+
"tgamma", "trunc",
923
]
1024
or
1125
allowComplex = false and
26+
numberOfParameters = 2 and
1227
result =
1328
[
14-
"atan2", "cbrt", "ceil", "copysign", "erf", "erfc", "exp2", "expm1", "fdim", "floor", "fma",
15-
"fmax", "fmin", "fmod", "frexp", "hypot", "ilogb", "ldexp", "lgamma", "llrint", "llround",
16-
"log10", "log1p", "log2", "logb", "lrint", "lround", "nearbyint", "nextafter", "nexttoward",
17-
"remainder", "remquo", "rint", "round", "scalbn", "scalbln", "tgamma", "trunc",
29+
"atan2", "copysign", "fdim", "fmax", "fmin", "fmod", "frexp", "hypot", "ldexp", "nextafter",
30+
"nexttoward", "remainder", "scalbn", "scalbln"
1831
]
32+
or
33+
allowComplex = false and
34+
numberOfParameters = 3 and
35+
result = ["fma", "remquo"]
1936
}
2037

2138
private predicate hasOutputArgument(string macroName, int index) {
@@ -27,19 +44,41 @@ private predicate hasOutputArgument(string macroName, int index) {
2744
class TgMathInvocation extends MacroInvocation {
2845
Call call;
2946
boolean allowComplex;
47+
int numberOfParameters;
3048

3149
TgMathInvocation() {
32-
this.getMacro().getName() = getATgMathMacroName(allowComplex) and
50+
this.getMacro().getName() = getATgMathMacroName(allowComplex, numberOfParameters) and
3351
call = getBestCallInExpansion(this)
3452
}
3553

54+
/** Account for extra parameters added by gcc */
55+
private int getParameterOffset() {
56+
// Gcc calls look something like: `__builtin_tgmath(cosf, cosd, cosl, arg)`, in this example
57+
// there is a parameter offset of 3, so `getOperandArgument(0)` is equivalent to
58+
// `call.getArgument(3)`.
59+
result = call.getNumberOfArguments() - numberOfParameters
60+
}
61+
3662
Expr getOperandArgument(int i) {
37-
result = call.getArgument(i) and
38-
not hasOutputArgument(call.getTarget().getName(), i)
63+
i >= 0 and
64+
result = call.getArgument(i + getParameterOffset()) and
65+
//i in [0..numberOfParameters - 1] and
66+
not hasOutputArgument(getMacro().getName(), i)
67+
}
68+
69+
/** Get all explicit conversions, except those added by clang in the macro body */
70+
Expr getExplicitlyConvertedOperandArgument(int i) {
71+
exists(Expr explicitConv |
72+
explicitConv = getOperandArgument(i).getExplicitlyConverted() and
73+
// clang explicitly casts most arguments, but not some integer arguments such as in `scalbn`.
74+
if call.getTarget().getName().matches("__tg_%") and explicitConv instanceof Conversion
75+
then result = explicitConv.(Conversion).getExpr()
76+
else result = explicitConv
77+
)
3978
}
4079

4180
int getNumberOfOperandArguments() {
42-
result = call.getNumberOfArguments() - count(int i | hasOutputArgument(getMacroName(), i))
81+
result = numberOfParameters - count(int i | hasOutputArgument(getMacroName(), i))
4382
}
4483

4584
Expr getAnOperandArgument() { result = getOperandArgument(_) }

Diff for: c/common/test/rules/functionnoreturnattributecondition/test.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ _Noreturn void test_noreturn_f10(int i) { // COMPLIANT
7777
case 4:
7878
thrd_exit(0);
7979
break;
80-
default:
80+
default:;
8181
jmp_buf jb;
8282
longjmp(jb, 0);
8383
}

Diff for: c/misra/src/rules/RULE-13-2/UnsequencedAtomicReads.ql

+38-12
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ import semmle.code.cpp.dataflow.TaintTracking
1717
import codingstandards.c.misra
1818
import codingstandards.c.Ordering
1919
import codingstandards.c.orderofevaluation.VariableAccessOrdering
20+
import codingstandards.cpp.StdFunctionOrMacro
2021

2122
class AtomicAccessInFullExpressionOrdering extends Ordering::Configuration {
2223
AtomicAccessInFullExpressionOrdering() { this = "AtomicAccessInFullExpressionOrdering" }
2324

2425
override predicate isCandidate(Expr e1, Expr e2) {
2526
exists(AtomicVariableAccess a, AtomicVariableAccess b, FullExpr e | a = e1 and b = e2 |
2627
a.getTarget() = b.getTarget() and
27-
a.(ConstituentExpr).getFullExpr() = e and
28-
b.(ConstituentExpr).getFullExpr() = e and
28+
a.getARead().(ConstituentExpr).getFullExpr() = e and
29+
b.getARead().(ConstituentExpr).getFullExpr() = e and
2930
not a = b
3031
)
3132
}
@@ -39,21 +40,40 @@ class AtomicAccessInFullExpressionOrdering extends Ordering::Configuration {
3940
class AtomicVariableAccess extends VariableAccess {
4041
AtomicVariableAccess() { getTarget().getType().hasSpecifier("atomic") }
4142

42-
/* Get the `atomic_<read|write>()` call this VarAccess occurs in. */
43-
FunctionCall getAtomicFunctionCall() {
44-
exists(AddressOfExpr addrParent, FunctionCall fc |
45-
fc.getTarget().getName().matches("__c11_atomic%") and
43+
/* Get the `atomic_load()` call this VarAccess occurs in. */
44+
Expr getAtomicFunctionRead() {
45+
exists(AddressOfExpr addrParent, AtomicReadOrWriteCall fc |
46+
fc.getName().matches("atomic_load%") and
47+
// StdFunctionOrMacro arguments are not necessarily reliable, so we look for any AddressOfExpr
48+
// that is an argument to a call to `atomic_load`.
4649
addrParent = fc.getArgument(0) and
4750
addrParent.getAnOperand() = this and
48-
result = fc
51+
result = fc.getExpr()
52+
)
53+
}
54+
55+
/* Get the `atomic_store()` call this VarAccess occurs in. */
56+
Expr getAtomicFunctionWrite(Expr storedValue) {
57+
exists(AddressOfExpr addrParent, AtomicReadOrWriteCall fc |
58+
addrParent = fc.getArgument(0) and
59+
addrParent.getAnOperand() = this and
60+
result = fc.getExpr() and
61+
(
62+
fc.getName().matches(["%store%", "%exchange%", "%fetch_%"]) and
63+
not fc.getName().matches("%compare%") and
64+
storedValue = fc.getArgument(1)
65+
or
66+
fc.getName().matches(["%compare%"]) and
67+
storedValue = fc.getArgument(2)
68+
)
4969
)
5070
}
5171

5272
/**
5373
* Gets an assigned expr, either in the form `x = <result>` or `atomic_store(&x, <result>)`.
5474
*/
5575
Expr getAnAssignedExpr() {
56-
result = getAtomicFunctionCall().getArgument(1)
76+
exists(getAtomicFunctionWrite(result))
5777
or
5878
exists(AssignExpr assign |
5979
assign.getLValue() = this and
@@ -65,19 +85,25 @@ class AtomicVariableAccess extends VariableAccess {
6585
* Gets the expression holding this variable access, either in the form `x` or `atomic_read(&x)`.
6686
*/
6787
Expr getARead() {
68-
result = getAtomicFunctionCall()
88+
result = getAtomicFunctionRead()
6989
or
7090
result = this
7191
}
7292
}
7393

7494
from
7595
AtomicAccessInFullExpressionOrdering config, FullExpr e, Variable v, AtomicVariableAccess va1,
76-
AtomicVariableAccess va2
96+
AtomicVariableAccess va2, Expr va1Read, Expr va2Read
7797
where
7898
not isExcluded(e, SideEffects3Package::unsequencedAtomicReadsQuery()) and
79-
e = va1.(ConstituentExpr).getFullExpr() and
80-
config.isUnsequenced(va1, va2) and
99+
va1Read = va1.getARead() and
100+
va2Read = va2.getARead() and
101+
e = va1Read.(ConstituentExpr).getFullExpr() and
102+
// Careful here. The `VariableAccess` in a pair of atomic function calls may not be unsequenced,
103+
// for instance in gcc where atomic functions expand to StmtExprs, which have clear sequences.
104+
// In this case, the result of `getARead()` for a pair of atomic function calls may be
105+
// unsequenced even though the `VariableAccess`es within those calls are not.
106+
config.isUnsequenced(va1Read, va2Read) and
81107
v = va1.getTarget() and
82108
v = va2.getTarget() and
83109
// Exclude cases where the variable is assigned a value tainted by the other variable access.

Diff for: c/misra/src/rules/RULE-21-22/TgMathArgumentWithInvalidEssentialType.ql

+7-4
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,17 @@ string getAllowedTypesString(TgMathInvocation call) {
3434
else result = "essentially signed, unsigned, or real floating type"
3535
}
3636

37-
from TgMathInvocation call, Expr arg, int argIndex, Type type, EssentialTypeCategory category
37+
from TgMathInvocation call, Expr convertedArg, Expr unconverted, int argIndex, Type type, EssentialTypeCategory category
3838
where
3939
not isExcluded(call, EssentialTypes2Package::tgMathArgumentWithInvalidEssentialTypeQuery()) and
40-
arg = call.getOperandArgument(argIndex) and
41-
type = getEssentialType(arg) and
40+
// We must handle conversions specially, as clang inserts casts in the macro body we want to ignore.
41+
convertedArg = call.getExplicitlyConvertedOperandArgument(argIndex) and
42+
unconverted = convertedArg.getUnconverted() and
43+
// Do not use `convertedArg.getEssentialType()`, as that is affected by clang's casts in the macro body.
44+
type = getEssentialTypeBeforeConversions(convertedArg) and
4245
category = getEssentialTypeCategory(type) and
4346
not category = getAnAllowedEssentialTypeCategory(call)
44-
select arg,
47+
select unconverted,
4548
"Argument " + (argIndex + 1) + " provided to type-generic macro '" + call.getMacroName() +
4649
"' has " + category.toString().toLowerCase() + ", which is not " + getAllowedTypesString(call) +
4750
"."

Diff for: c/misra/src/rules/RULE-21-23/TgMathArgumentsWithDifferingStandardType.ql

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ Type canonicalize(Type type) {
5959
}
6060

6161
Type getEffectiveStandardType(Expr e) {
62-
result = canonicalize(getPromotedType(e.getExplicitlyConverted()))
62+
result = canonicalize(getPromotedType(e))
6363
}
6464

6565
from TgMathInvocation call, Type firstType
6666
where
6767
not isExcluded(call, EssentialTypes2Package::tgMathArgumentsWithDifferingStandardTypeQuery()) and
68-
firstType = getEffectiveStandardType(call.getAnOperandArgument()) and
69-
not forall(Expr arg | arg = call.getAnOperandArgument() |
68+
firstType = getEffectiveStandardType(call.getExplicitlyConvertedOperandArgument(0)) and
69+
not forall(Expr arg | arg = call.getExplicitlyConvertedOperandArgument(_) |
7070
firstType = getEffectiveStandardType(arg)
7171
)
7272
select call,

Diff for: c/misra/src/rules/RULE-21-25/InvalidMemoryOrderArgument.ql

+17-59
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,23 @@
1414

1515
import cpp
1616
import codingstandards.c.misra
17+
import codingstandards.cpp.StdFunctionOrMacro
1718
import semmle.code.cpp.dataflow.new.DataFlow
1819

20+
class MemoryOrderEnum extends Enum {
21+
MemoryOrderEnum() {
22+
this.hasGlobalOrStdName("memory_order")
23+
or
24+
exists(TypedefType t |
25+
t.getName() = "memory_order" and
26+
t.getBaseType() = this
27+
)
28+
}
29+
}
30+
1931
/* A member of the set of memory orders defined in the `memory_order` enum */
2032
class MemoryOrder extends EnumConstant {
21-
MemoryOrder() { getDeclaringEnum().getName() = "memory_order" }
33+
MemoryOrder() { getDeclaringEnum() instanceof MemoryOrderEnum }
2234

2335
int getIntValue() { result = getValue().toInt() }
2436
}
@@ -49,59 +61,6 @@ class MemoryOrderConstantExpr extends Expr {
4961
string getMemoryOrderString() { result = ord.getName() }
5062
}
5163

52-
/**
53-
* A `stdatomic.h` function which accepts a `memory_order` value as a parameter.
54-
*/
55-
class MemoryOrderedStdAtomicFunction extends Function {
56-
int orderParamIdx;
57-
58-
MemoryOrderedStdAtomicFunction() {
59-
exists(int baseParamIdx, int baseParams, string prefix, string regex, string basename |
60-
regex = "__(c11_)?atomic_([a-z_]+)" and
61-
prefix = getName().regexpCapture(regex, 1) and
62-
basename = "atomic_" + getName().regexpCapture(regex, 2) + ["", "_explicit"] and
63-
(
64-
basename in ["atomic_thread_fence", "atomic_signal_fence"] and
65-
baseParamIdx = 0 and
66-
baseParams = 1
67-
or
68-
basename in ["atomic_load", "atomic_flag_clear", "atomic_flag_test_and_set"] and
69-
baseParamIdx = 1 and
70-
baseParams = 2
71-
or
72-
basename in [
73-
"atomic_store", "atomic_fetch_" + ["add", "sub", "or", "xor", "and"], "atomic_exchange"
74-
] and
75-
baseParamIdx = 2 and
76-
baseParams = 3
77-
or
78-
basename in ["atomic_compare_exchange_" + ["strong", "weak"]] and
79-
baseParamIdx = [3, 4] and
80-
baseParams = 5
81-
) and
82-
(
83-
// GCC case, may have one or two inserted parameters, e.g.:
84-
// __atomic_load(8, &repr->a, &desired, order)
85-
// or
86-
// __atomic_load_8(&repr->a, &desired, order)
87-
prefix = "" and
88-
exists(int extraParams |
89-
extraParams = getNumberOfParameters() - baseParams and
90-
extraParams >= 0 and
91-
orderParamIdx = baseParamIdx + extraParams
92-
)
93-
or
94-
// Clang case, no inserted parameters:
95-
// __c11_atomic_load(object, order)
96-
prefix = "c11_" and
97-
orderParamIdx = baseParamIdx
98-
)
99-
)
100-
}
101-
102-
int getOrderParameterIdx() { result = orderParamIdx }
103-
}
104-
10564
module MemoryOrderFlowConfig implements DataFlow::ConfigSig {
10665
predicate isSource(DataFlow::Node node) {
10766
// Direct usage of memory order constant
@@ -118,9 +77,8 @@ module MemoryOrderFlowConfig implements DataFlow::ConfigSig {
11877
}
11978

12079
predicate isSink(DataFlow::Node node) {
121-
exists(FunctionCall fc |
122-
node.asExpr() =
123-
fc.getArgument(fc.getTarget().(MemoryOrderedStdAtomicFunction).getOrderParameterIdx())
80+
exists(AtomicallySequencedCall call |
81+
call.getAMemoryOrderArgument() = node.asExpr()
12482
)
12583
}
12684
}
@@ -140,7 +98,7 @@ string describeMemoryOrderNode(DataFlow::Node node) {
14098
}
14199

142100
from
143-
Expr argument, Function function, string value, MemoryOrderFlow::PathNode source,
101+
Expr argument, AtomicallySequencedCall function, string value, MemoryOrderFlow::PathNode source,
144102
MemoryOrderFlow::PathNode sink
145103
where
146104
not isExcluded(argument, Concurrency6Package::invalidMemoryOrderArgumentQuery()) and
@@ -149,6 +107,6 @@ where
149107
value = describeMemoryOrderNode(source.getNode()) and
150108
// Double check that we didn't find flow from something equivalent to the allowed value.
151109
not value = any(AllowedMemoryOrder e).getName() and
152-
function.getACallToThisFunction().getAnArgument() = argument
110+
function.getAMemoryOrderArgument() = argument
153111
select argument, source, sink, "Invalid memory order '$@' in call to function '$@'.", value, value,
154112
function, function.getName()

Diff for: c/misra/src/rules/RULE-9-7/UninitializedAtomicObject.ql

+5-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ class ThreadSpawningFunction extends Function {
3131
}
3232

3333
class AtomicInitAddressOfExpr extends AddressOfExpr {
34-
AtomicInitAddressOfExpr() { exists(AtomicInitCall c | this = c.getArgument(0)) }
34+
AtomicInitAddressOfExpr() {
35+
// StdFunctionOrMacro arguments are not necessarily reliable, so we look for any AddressOfExpr
36+
// that is an argument to a call to `atomic_init`.
37+
exists(AtomicInitCall c | this = c.getAnArgument())
38+
}
3539
}
3640

3741
ControlFlowNode getARequiredInitializationPoint(LocalScopeVariable v) {

Diff for: c/misra/test/rules/DIR-5-1/test.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void many_thread13_calls_nonreentrant_funcs(void *p) {
9999
wcsrtombs(NULL, NULL, 0, NULL); // NON-COMPLIANT
100100
}
101101

102-
void main() {
102+
int main(int argc, char *argv[]) {
103103
thrd_t single_thread1;
104104
thrd_t many_thread2;
105105
thrd_t single_thread3;

Diff for: c/misra/test/rules/DIR-5-3/test.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ void func_called_from_main(void);
1414
void make_threads_called_from_func_called_from_main(void);
1515
void make_threads_called_from_main_pthread_thrd(void);
1616

17-
void main() {
17+
int main(int argc, char *argv[]) {
1818
thrd_create(&g1, &thrd_func, NULL); // COMPLIANT
1919
pthread_create(&g2, NULL, &pthread_func, NULL); // COMPLIANT
2020

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| test.c:29:18:29:36 | ATOMIC_VAR_INIT(VALUE) | Usage of macro ATOMIC_VAR_INIT() is declared obscelescent in C18, and discouraged in earlier C versions. |

Diff for: c/misra/test/rules/RULE-11-10/AtomicQualifierAppliedToVoid.expected.clang

Whitespace-only changes.

0 commit comments

Comments
 (0)