Skip to content

Commit a088de2

Browse files
Merge pull request #884 from github/michaelrfairhurst/address-compiler-compatibility-issue-misra-c-2023
First pass at addressing cross-compiler compatibility in MISRA 2023.
2 parents 70f0077 + b9e1c0b commit a088de2

File tree

41 files changed

+1736
-273
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

+1736
-273
lines changed

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(_) }

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
}

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.

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,19 @@ 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
38+
TgMathInvocation call, Expr convertedArg, Expr unconverted, int argIndex, Type type,
39+
EssentialTypeCategory category
3840
where
3941
not isExcluded(call, EssentialTypes2Package::tgMathArgumentWithInvalidEssentialTypeQuery()) and
40-
arg = call.getOperandArgument(argIndex) and
41-
type = getEssentialType(arg) and
42+
// We must handle conversions specially, as clang inserts casts in the macro body we want to ignore.
43+
convertedArg = call.getExplicitlyConvertedOperandArgument(argIndex) and
44+
unconverted = convertedArg.getUnconverted() and
45+
// Do not use `convertedArg.getEssentialType()`, as that is affected by clang's casts in the macro body.
46+
type = getEssentialTypeBeforeConversions(convertedArg) and
4247
category = getEssentialTypeCategory(type) and
4348
not category = getAnAllowedEssentialTypeCategory(call)
44-
select arg,
49+
select unconverted,
4550
"Argument " + (argIndex + 1) + " provided to type-generic macro '" + call.getMacroName() +
4651
"' has " + category.toString().toLowerCase() + ", which is not " + getAllowedTypesString(call) +
4752
"."

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

+3-5
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,13 @@ Type canonicalize(Type type) {
5858
else result = type
5959
}
6060

61-
Type getEffectiveStandardType(Expr e) {
62-
result = canonicalize(getPromotedType(e.getExplicitlyConverted()))
63-
}
61+
Type getEffectiveStandardType(Expr e) { result = canonicalize(getPromotedType(e)) }
6462

6563
from TgMathInvocation call, Type firstType
6664
where
6765
not isExcluded(call, EssentialTypes2Package::tgMathArgumentsWithDifferingStandardTypeQuery()) and
68-
firstType = getEffectiveStandardType(call.getAnOperandArgument()) and
69-
not forall(Expr arg | arg = call.getAnOperandArgument() |
66+
firstType = getEffectiveStandardType(call.getExplicitlyConvertedOperandArgument(0)) and
67+
not forall(Expr arg | arg = call.getExplicitlyConvertedOperandArgument(_) |
7068
firstType = getEffectiveStandardType(arg)
7169
)
7270
select call,

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

+16-60
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,10 +77,7 @@ 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())
124-
)
80+
exists(AtomicallySequencedCall call | call.getAMemoryOrderArgument() = node.asExpr())
12581
}
12682
}
12783

@@ -140,7 +96,7 @@ string describeMemoryOrderNode(DataFlow::Node node) {
14096
}
14197

14298
from
143-
Expr argument, Function function, string value, MemoryOrderFlow::PathNode source,
99+
Expr argument, AtomicallySequencedCall function, string value, MemoryOrderFlow::PathNode source,
144100
MemoryOrderFlow::PathNode sink
145101
where
146102
not isExcluded(argument, Concurrency6Package::invalidMemoryOrderArgumentQuery()) and
@@ -149,6 +105,6 @@ where
149105
value = describeMemoryOrderNode(source.getNode()) and
150106
// Double check that we didn't find flow from something equivalent to the allowed value.
151107
not value = any(AllowedMemoryOrder e).getName() and
152-
function.getACallToThisFunction().getAnArgument() = argument
108+
function.getAMemoryOrderArgument() = argument
153109
select argument, source, sink, "Invalid memory order '$@' in call to function '$@'.", value, value,
154110
function, function.getName()

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) {

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;

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. |

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

Whitespace-only changes.

0 commit comments

Comments
 (0)