@specs-feup/clava-misra 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CxxSources/lib.cpp +3 -0
- package/CxxSources/lib.h +8 -0
- package/CxxSources/main.cpp +40 -0
- package/README.md +32 -0
- package/TODO.md +1 -0
- package/consumer_order.txt +2 -0
- package/enum_integer_type.txt +0 -0
- package/is_temporary.txt +0 -0
- package/jest.config.js +25 -0
- package/omp.txt +0 -0
- package/package.json +23 -0
- package/src/foo.ts +6 -0
- package/src/main.ts +36 -0
- package/src/misra/MISRAAnalyser.ts +43 -0
- package/src/misra/MISRAPass.ts +85 -0
- package/src/misra/MISRAPassResult.ts +20 -0
- package/src/misra/MISRAReporter.ts +57 -0
- package/src/misra/passes/S10_EssentialTypePass.ts +395 -0
- package/src/misra/passes/S12_ExpressionPass.ts +86 -0
- package/src/misra/passes/S13_SideEffectPass.ts +121 -0
- package/src/misra/passes/S15_ControlFlowPass.ts +108 -0
- package/src/misra/passes/S16_SwitchStatementPass.ts +168 -0
- package/src/misra/passes/S17_FunctionPass.ts +43 -0
- package/src/misra/passes/S18_PointersArraysPass.ts +126 -0
- package/src/misra/passes/S19_OverlappingStoragePass.ts +27 -0
- package/src/misra/passes/S21_StandardLibPass.ts +92 -0
- package/src/misra/passes/S3_CommentPass.ts +40 -0
- package/src/misra/passes/S5_IdentifierPass.ts +66 -0
- package/src/misra/passes/S6_TypePass.ts +32 -0
- package/src/misra/passes/S7_LiteralsConstantsPass.ts +80 -0
- package/src/misra/passes/S8_DeclDefPass.ts +138 -0
- package/src/misra/sections/Section10_EssentialTypeModel.ts +377 -0
- package/src/misra/sections/Section11_PointerTypeConversions.ts +103 -0
- package/src/misra/sections/Section12_Expressions.ts +80 -0
- package/src/misra/sections/Section13_SideEffects.ts +100 -0
- package/src/misra/sections/Section14_ControlStmtExprs.ts +27 -0
- package/src/misra/sections/Section15_ControlFlow.ts +114 -0
- package/src/misra/sections/Section16_SwitchStatements.ts +154 -0
- package/src/misra/sections/Section17_Functions.ts +33 -0
- package/src/misra/sections/Section18_PointersAndArrays.ts +108 -0
- package/src/misra/sections/Section19_OverlappingStorage.ts +18 -0
- package/src/misra/sections/Section20_PreprocessingDirectives.ts +22 -0
- package/src/misra/sections/Section21_StandardLibraries.ts +65 -0
- package/src/misra/sections/Section2_UnusedCode.ts +47 -0
- package/src/misra/sections/Section3_Comments.ts +23 -0
- package/src/misra/sections/Section5_Identifiers.ts +149 -0
- package/src/misra/sections/Section6_Types.ts +26 -0
- package/src/misra/sections/Section7_LiteralsConstants.ts +76 -0
- package/src/misra/sections/Section8_DeclarationsDefinitions.ts +133 -0
- package/src/misra/tests/S10_EssentialTypes.test.ts +253 -0
- package/src/misra/tests/S12_Expressions.test.ts +43 -0
- package/src/misra/tests/S13_SideEffects.test.ts +77 -0
- package/src/misra/tests/S15_ControlFlow.test.ts +144 -0
- package/src/misra/tests/S16_SwitchStatements.test.ts +164 -0
- package/src/misra/tests/S17_Functions.test.ts +46 -0
- package/src/misra/tests/S18_PointersArrays.test.ts +167 -0
- package/src/misra/tests/S19_OverlappingStorage.test.ts +38 -0
- package/src/misra/tests/S3_Comments.test.ts +36 -0
- package/src/misra/tests/S6_Types.test.ts +36 -0
- package/src/misra/tests/S7_LiteralsConstants.test.ts +48 -0
- package/src/misra/tests/utils.ts +47 -0
- package/tsconfig.jest.json +5 -0
- package/tsconfig.json +18 -0
- package/typedoc.config.js +6 -0
- package/types_with_templates.txt +0 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
import Query from "@specs-feup/lara/api/weaver/Query.js";
|
2
|
+
import { Program, FileJp, Cast, FunctionType, PointerType, BuiltinType, IntLiteral, Joinpoint, QualType } from "@specs-feup/clava/api/Joinpoints.js";
|
3
|
+
import MISRAAnalyser from "../MISRAAnalyser.js";
|
4
|
+
import Section10_EssentialTypeModel, { EssentialTypes } from "./Section10_EssentialTypeModel.js";
|
5
|
+
|
6
|
+
export default class Section11_PointerTypeConversions extends MISRAAnalyser {
|
7
|
+
ruleMapper: Map<number, (jp: Program | FileJp) => void>;
|
8
|
+
|
9
|
+
constructor(rules: number[]) {
|
10
|
+
super(rules);
|
11
|
+
this.ruleMapper = new Map([
|
12
|
+
[7, this.r11_7_noFloatConversions.bind(this)]
|
13
|
+
]);
|
14
|
+
}
|
15
|
+
|
16
|
+
private static functionTypesMatch(t1: FunctionType, t2: FunctionType) {
|
17
|
+
console.log(t1.returnType.kind, t2.returnType.kind);
|
18
|
+
if (t1.returnType != t2.returnType) {
|
19
|
+
return false;
|
20
|
+
}
|
21
|
+
if (t1.paramTypes.length !== t2.paramTypes.length) {
|
22
|
+
return false;
|
23
|
+
}
|
24
|
+
for (let i = 0; i < t1.paramTypes.length; i++) {
|
25
|
+
if (t1.paramTypes[i] != t2.paramTypes[i]) {
|
26
|
+
return false;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
return true;
|
31
|
+
}
|
32
|
+
|
33
|
+
private r11_1_noFunctionPointerConversions($startNode: Joinpoint) { //type equality not working
|
34
|
+
Query.searchFrom($startNode, Cast).get().forEach(cast => {
|
35
|
+
const fromType = cast.fromType.desugarAll;
|
36
|
+
const toType = cast.toType.desugarAll;
|
37
|
+
|
38
|
+
if (fromType instanceof PointerType && toType instanceof PointerType && fromType.pointee.desugarAll instanceof FunctionType && toType.pointee.desugarAll instanceof FunctionType) {
|
39
|
+
console.log("hello");
|
40
|
+
if (!Section11_PointerTypeConversions.functionTypesMatch(fromType.pointee.desugarAll, toType.pointee.desugarAll)) {
|
41
|
+
this.logMISRAError(cast, "A function pointer can only be converted into another function pointer if the types match.");
|
42
|
+
|
43
|
+
}
|
44
|
+
}
|
45
|
+
else if (fromType instanceof PointerType && fromType.pointee.desugarAll instanceof FunctionType) {
|
46
|
+
if (!(toType instanceof BuiltinType && toType.isVoid)) {
|
47
|
+
this.logMISRAError(cast, "A function pointer can only be converted into another function pointer if the types match.");
|
48
|
+
}
|
49
|
+
}
|
50
|
+
else if (toType instanceof PointerType && toType.pointee.desugarAll instanceof FunctionType) {
|
51
|
+
if (!(cast.subExpr instanceof IntLiteral && Number(cast.subExpr.value) === 0)) {
|
52
|
+
this.logMISRAError(cast, "Only null pointer constants can be converted into function pointers.");
|
53
|
+
}
|
54
|
+
}
|
55
|
+
});
|
56
|
+
}
|
57
|
+
|
58
|
+
private r11_4_noIntToPointer($startNode: Joinpoint) {
|
59
|
+
Query.searchFrom($startNode, Cast).get().forEach(cast => {
|
60
|
+
const fromType = cast.fromType.desugarAll;
|
61
|
+
const toType = cast.toType.desugarAll;
|
62
|
+
if (fromType.isPointer !== toType.isPointer) {
|
63
|
+
this.logMISRAError(cast, "Integers should not be converted to pointers and vice-versa.");
|
64
|
+
}
|
65
|
+
})
|
66
|
+
}
|
67
|
+
|
68
|
+
private r11_5_noConversionFromVoid($startNode: Joinpoint) {
|
69
|
+
Query.searchFrom($startNode, Cast).get().forEach(cast => {
|
70
|
+
const fromType = cast.fromType.desugarAll;
|
71
|
+
const toType =cast.toType.desugarAll;
|
72
|
+
if (fromType instanceof PointerType && fromType.pointee instanceof BuiltinType && fromType.pointee.isVoid
|
73
|
+
&& toType instanceof PointerType && !(toType.pointee instanceof BuiltinType && toType.pointee.isVoid)) {
|
74
|
+
this.logMISRAError(cast, "Pointer to void should not be converted to pointer to object");
|
75
|
+
}
|
76
|
+
});
|
77
|
+
}
|
78
|
+
|
79
|
+
private r11_7_noFloatConversions($startNode: Joinpoint) {
|
80
|
+
Query.searchFrom($startNode, Cast).get().forEach(cast => {
|
81
|
+
let fromType = cast.fromType.desugarAll;
|
82
|
+
let toType = cast.fromType.desugarAll;
|
83
|
+
|
84
|
+
if (fromType instanceof QualType) {
|
85
|
+
fromType = fromType.unqualifiedType.desugarAll;
|
86
|
+
}
|
87
|
+
if (toType instanceof QualType) {
|
88
|
+
toType = toType.unqualifiedType.desugarAll;
|
89
|
+
}
|
90
|
+
|
91
|
+
if (fromType instanceof PointerType && !(toType instanceof PointerType)
|
92
|
+
&& Section10_EssentialTypeModel.getEssentialType(toType) !== EssentialTypes.SIGNED
|
93
|
+
&& Section10_EssentialTypeModel.getEssentialType(toType) !== EssentialTypes.UNSIGNED) {
|
94
|
+
this.logMISRAError(cast, "A pointer to object cannot be cast to a non-integer arithmetic type.");
|
95
|
+
}
|
96
|
+
else if (toType instanceof PointerType && !(fromType instanceof PointerType)
|
97
|
+
&& Section10_EssentialTypeModel.getEssentialType(fromType) !== EssentialTypes.SIGNED
|
98
|
+
&& Section10_EssentialTypeModel.getEssentialType(fromType) !== EssentialTypes.UNSIGNED) {
|
99
|
+
this.logMISRAError(cast, "A non-arithmetic integer value cannot be cast to a pointer to object.");
|
100
|
+
}
|
101
|
+
}, this);
|
102
|
+
}
|
103
|
+
}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import Query from "@specs-feup/lara/api/weaver/Query.js";
|
2
|
+
import { Program, FileJp, BinaryOp, Joinpoint, Op, Loop, Expression } from "@specs-feup/clava/api/Joinpoints.js";
|
3
|
+
import MISRAAnalyser from "../MISRAAnalyser.js";
|
4
|
+
import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
|
5
|
+
import Fix from "@specs-feup/clava/api/clava/analysis/Fix.js";
|
6
|
+
|
7
|
+
export default class Section12_Expressions extends MISRAAnalyser {
|
8
|
+
ruleMapper: Map<number, (jp: Program | FileJp) => void>;
|
9
|
+
|
10
|
+
constructor(rules: number[]) {
|
11
|
+
super(rules);
|
12
|
+
this.ruleMapper = new Map([
|
13
|
+
[1, this.r12_1_explicitPrecedence.bind(this)],
|
14
|
+
[3, this.r12_3_noCommaOperator.bind(this)]
|
15
|
+
]);
|
16
|
+
}
|
17
|
+
|
18
|
+
private static isAdditive(op: string): boolean {
|
19
|
+
return op === "add" || op === "sub";
|
20
|
+
}
|
21
|
+
|
22
|
+
private static isMultiplicative(op: string): boolean {
|
23
|
+
return op === "mul" || op === "div" || op === "rem";
|
24
|
+
}
|
25
|
+
|
26
|
+
private static isShift(op: string): boolean {
|
27
|
+
return op === "shl" || op === "shr";
|
28
|
+
}
|
29
|
+
|
30
|
+
private static isRelational(op: string): boolean {
|
31
|
+
return op === "lt" || op === "gt" || op === "le" || op === "ge";
|
32
|
+
}
|
33
|
+
|
34
|
+
private static isEquality(op: string): boolean {
|
35
|
+
return op === "eq" || op === "ne";
|
36
|
+
}
|
37
|
+
|
38
|
+
private static isSamePrecedence(op1: string, op2: string): boolean {
|
39
|
+
if (op1 === op2) return true;
|
40
|
+
|
41
|
+
for (const fun of [this.isAdditive, this.isMultiplicative, this.isShift, this.isRelational, this.isEquality]) {
|
42
|
+
if (fun(op1) && fun(op2)) {
|
43
|
+
return true;
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
return false;
|
48
|
+
}
|
49
|
+
|
50
|
+
private r12_1_explicitPrecedence($startNode: Joinpoint) {
|
51
|
+
for (const bOp of Query.searchFrom($startNode, BinaryOp, {isAssignment: false})) {
|
52
|
+
if ((bOp.kind === "ptr_mem_d" || bOp.kind === "ptr_mem_i")
|
53
|
+
|| (!bOp.getAncestor("binaryOp") && !bOp.getAncestor("ternaryOp"))
|
54
|
+
|| (bOp.parent instanceof BinaryOp && bOp.parent.isAssignment)
|
55
|
+
|| (bOp.parent instanceof Op && Section12_Expressions.isSamePrecedence(bOp.kind, bOp.parent.kind))
|
56
|
+
|| (bOp.parent.instanceOf("parenExpr"))) continue;
|
57
|
+
|
58
|
+
this.logMISRAError(bOp, `Operator precedence in expression ${bOp.code} is not explicit.`, new Fix(bOp, ($jp: Joinpoint) => {
|
59
|
+
const parenExpr = ClavaJoinPoints.parenthesis($jp as Expression);
|
60
|
+
bOp.replaceWith(parenExpr);
|
61
|
+
}));
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
private r12_3_noCommaOperator($startNode: Joinpoint) {
|
66
|
+
Query.searchFrom($startNode, BinaryOp, {operator: ','}).get().forEach(op => {
|
67
|
+
const loopAncestor = op.getAncestor("loop");
|
68
|
+
if (loopAncestor instanceof Loop && (loopAncestor?.step?.contains(op) || loopAncestor?.cond?.contains(op) || loopAncestor?.init?.contains(op))) {
|
69
|
+
console.log(`Cannot eliminate comma operator in expression ${op.code} since it is at the head of a loop.`);
|
70
|
+
this.logMISRAError(op, "Use of the comma operator is not allowed.");
|
71
|
+
}
|
72
|
+
else {
|
73
|
+
this.logMISRAError(op, "Use of the comma operator is not allowed.", new Fix(op, ($jp: Joinpoint) => {
|
74
|
+
$jp.insertBefore(($jp as BinaryOp).left.stmt);
|
75
|
+
$jp.replaceWith(($jp as BinaryOp).right);
|
76
|
+
}));
|
77
|
+
}
|
78
|
+
}, this);
|
79
|
+
}
|
80
|
+
}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import Query from "@specs-feup/lara/api/weaver/Query.js";
|
2
|
+
import AnalyserResult from "@specs-feup/clava/api/clava/analysis/AnalyserResult.js";
|
3
|
+
import { Program, FileJp, Joinpoint, Varref, Call, QualType, Vardecl, UnaryOp, InitList, BinaryOp } from "@specs-feup/clava/api/Joinpoints.js";
|
4
|
+
import MISRAAnalyser from "../MISRAAnalyser.js";
|
5
|
+
import TraversalType from "@specs-feup/lara/api/weaver/TraversalType.js";
|
6
|
+
import Fix from "@specs-feup/clava/api/clava/analysis/Fix.js";
|
7
|
+
|
8
|
+
export default class Section13_SideEffects extends MISRAAnalyser {
|
9
|
+
ruleMapper: Map<number, (jp: Program | FileJp) => void>;
|
10
|
+
|
11
|
+
constructor(rules: number[]) {
|
12
|
+
super(rules);
|
13
|
+
this.ruleMapper = new Map([
|
14
|
+
[1, this.r13_1_initListSideEffects.bind(this)],
|
15
|
+
[3, this.r13_3_noIncrementSideEffects.bind(this)],
|
16
|
+
[4, this.r13_4_noUseOfAssignmentValue.bind(this)],
|
17
|
+
[5, this.r13_5_shortCircuitSideEffects.bind(this)]
|
18
|
+
]);
|
19
|
+
}
|
20
|
+
|
21
|
+
private checkPotentialPersistentSideEffects<T>($startNode: Joinpoint, type: any, filters: any, name: string, childFun: ($jp: T) => Joinpoint) {
|
22
|
+
Query.searchFrom($startNode, type, filters).get().forEach(list => {
|
23
|
+
Query.searchFromInclusive(childFun(list), Varref).get().forEach(ref => {
|
24
|
+
if (ref.type instanceof QualType && ref.type.qualifiers?.includes("volatile")) {
|
25
|
+
this.logMISRAError(list, `${name} ${list.code} contains persistent side effects: an access to volatile object ${ref.name}.`)
|
26
|
+
}
|
27
|
+
}, this);
|
28
|
+
Query.searchFromInclusive(childFun(list), Call).get().forEach(call => {
|
29
|
+
this.logMISRAError(list, `${name} ${list.code} may contain persistent side effects in call to ${call.name}.`);
|
30
|
+
}, this);
|
31
|
+
Query.searchFromInclusive(childFun(list), UnaryOp, {kind: /(post_inc)|(post_dec)|(pre_inc)|(pre_dec)/}).get().forEach(op => { //use chain?
|
32
|
+
Query.searchFrom(op, Varref).get().forEach(ref => {
|
33
|
+
if (ref.declaration instanceof Vardecl && ref.declaration.isGlobal) {
|
34
|
+
this.logMISRAError(list, `${name} ${list.code} may contain persistent side effects in expression ${op.code}.`)
|
35
|
+
}
|
36
|
+
});
|
37
|
+
}, this);
|
38
|
+
}, this);
|
39
|
+
}
|
40
|
+
|
41
|
+
private r13_1_initListSideEffects($startNode: Joinpoint) {
|
42
|
+
this.checkPotentialPersistentSideEffects<Joinpoint>($startNode, InitList, undefined, "Initializer list", jp => jp);
|
43
|
+
}
|
44
|
+
|
45
|
+
private static visitAllExprs(fun: ($jp: Joinpoint) => void, root: Joinpoint) {
|
46
|
+
let curr = root;
|
47
|
+
while (curr) {
|
48
|
+
const temp = curr;
|
49
|
+
//console.log(temp.joinPointType);
|
50
|
+
curr = curr.rightJp;
|
51
|
+
if (temp.instanceOf("expression")) {
|
52
|
+
fun(temp);
|
53
|
+
}
|
54
|
+
else Section13_SideEffects.visitAllExprs(fun, temp.children[0]);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
private checkIncrementSideEffects(exprRoot: Joinpoint) { //something wierd but mostly working if not for duplicates
|
59
|
+
const jps = Query.searchFrom(exprRoot, UnaryOp, {kind: /(post_inc)|(post_dec)|(pre_inc)|(pre_dec)/}, TraversalType.POSTORDER).get();
|
60
|
+
const calls = Query.searchFromInclusive(exprRoot, Call).get();
|
61
|
+
const assignments = Query.searchFromInclusive(exprRoot, BinaryOp, {isAssignment: true}).get();
|
62
|
+
if (jps.length + calls.length + assignments.length < 2) return;
|
63
|
+
|
64
|
+
this.logMISRAError(exprRoot, `Expression ${exprRoot.code} contains a pre/post inc/decrement operator and other side effects.`, new Fix(exprRoot, ($jp: Joinpoint) => {
|
65
|
+
const jps = Query.searchFrom($jp, UnaryOp, {kind: /(post_inc)|(post_dec)|(pre_inc)|(pre_dec)/}, TraversalType.POSTORDER).get();
|
66
|
+
const calls = Query.searchFromInclusive($jp, Call).get();
|
67
|
+
const assignments = Query.searchFromInclusive($jp, BinaryOp, {isAssignment: true}).get();
|
68
|
+
|
69
|
+
const transformationNo = (calls.length === 0 && assignments.length === 0) ? jps.length - 1 : jps.length;
|
70
|
+
|
71
|
+
for (let i = 0; i < transformationNo; i++) {
|
72
|
+
const jp = jps[i];
|
73
|
+
if (/post_.*/.test(jp.kind)) {
|
74
|
+
$jp.insertAfter(jp.deepCopy());
|
75
|
+
}
|
76
|
+
else {
|
77
|
+
$jp.insertBefore(jp.deepCopy());
|
78
|
+
}
|
79
|
+
console.log(jp);
|
80
|
+
jp.replaceWith(jp.operand);
|
81
|
+
}
|
82
|
+
}));
|
83
|
+
}
|
84
|
+
|
85
|
+
private r13_3_noIncrementSideEffects($startNode: Joinpoint) {
|
86
|
+
Section13_SideEffects.visitAllExprs(this.checkIncrementSideEffects, $startNode);
|
87
|
+
}
|
88
|
+
|
89
|
+
private r13_4_noUseOfAssignmentValue($startNode: Joinpoint) {
|
90
|
+
for (const bOp of Query.searchFrom($startNode, BinaryOp, {isAssignment: true})) {
|
91
|
+
if (!bOp.parent.instanceOf("exprStmt") && !(bOp.parent.instanceOf("parenExpr") && bOp.parent?.parent?.instanceOf("exprStmt"))) {
|
92
|
+
this.logMISRAError(bOp, `Value of assignment expression ${bOp.code} should not be used.`);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
private r13_5_shortCircuitSideEffects($startNode: Joinpoint) {
|
98
|
+
this.checkPotentialPersistentSideEffects<BinaryOp>($startNode, BinaryOp, {operator: /(\&\&|\|\|)/}, "RHS of && or || expression", jp => jp.right);
|
99
|
+
}
|
100
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import Query from "@specs-feup/lara/api/weaver/Query.js";
|
2
|
+
import { Program, FileJp, Loop, Joinpoint, If, ExprStmt } from "@specs-feup/clava/api/Joinpoints.js";
|
3
|
+
import MISRAAnalyser from "../MISRAAnalyser.js";
|
4
|
+
|
5
|
+
export default class Section14_ControlStmtExprs extends MISRAAnalyser {
|
6
|
+
ruleMapper: Map<number, (jp: Program | FileJp) => void>;
|
7
|
+
|
8
|
+
constructor(rules: number[]) {
|
9
|
+
super(rules);
|
10
|
+
this.ruleMapper = new Map([
|
11
|
+
[4, this.r14_4_essentiallyBooleanInControllingExpr.bind(this)]
|
12
|
+
]);
|
13
|
+
}
|
14
|
+
|
15
|
+
private r14_4_essentiallyBooleanInControllingExpr($startNode: Joinpoint) { //better way?
|
16
|
+
Query.searchFrom($startNode, Loop).get().forEach(loop => {
|
17
|
+
if ((loop.cond as ExprStmt).expr.type.code !== "bool") {
|
18
|
+
this.logMISRAError(loop, `Loop controlling expression ${(loop.cond as ExprStmt).expr.code} does not have essentially boolean type.`);
|
19
|
+
}
|
20
|
+
}, this);
|
21
|
+
Query.searchFrom($startNode, If).get().forEach(ifStmt => {
|
22
|
+
if (ifStmt.cond.type.code !== "bool") {
|
23
|
+
this.logMISRAError(ifStmt, `Loop controlling expression ${ifStmt.cond.code} does not have essentially boolean type.`);
|
24
|
+
}
|
25
|
+
}, this);
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import Query from "@specs-feup/lara/api/weaver/Query.js";
|
2
|
+
import { Program, FileJp, GotoStmt, Joinpoint, LabelStmt, Break } from "@specs-feup/clava/api/Joinpoints.js";
|
3
|
+
import MISRAAnalyser from "../MISRAAnalyser.js";
|
4
|
+
|
5
|
+
export default class Section15_ControlFlow extends MISRAAnalyser {
|
6
|
+
ruleMapper: Map<number, (jp: Program | FileJp) => void>;
|
7
|
+
|
8
|
+
constructor(rules: number[]) {
|
9
|
+
super(rules);
|
10
|
+
this.ruleMapper = new Map([
|
11
|
+
[1, this.r15_1_noGoto.bind(this)],
|
12
|
+
[2, this.r15_2_noBackJumps.bind(this)],
|
13
|
+
[3, this.r15_3_gotoBlockEnclosed.bind(this)],
|
14
|
+
[4, this.r15_4_loopSingleBreak.bind(this)]
|
15
|
+
]);
|
16
|
+
}
|
17
|
+
|
18
|
+
private r15_1_noGoto($startNode: Joinpoint) {
|
19
|
+
Query.searchFrom($startNode, GotoStmt).get().forEach(goto => this.logMISRAError(goto, "goto statements should not be used."), this);
|
20
|
+
|
21
|
+
return [];
|
22
|
+
}
|
23
|
+
|
24
|
+
private static isBeforeInCode(line1: number, col1: number, line2: number, col2: number): boolean {
|
25
|
+
if (line1 < line2) return true;
|
26
|
+
else return col1 < col2;
|
27
|
+
}
|
28
|
+
|
29
|
+
private r15_2_noBackJumps($startNode: Joinpoint) {
|
30
|
+
for (const gotoStmt of Query.searchFrom($startNode, GotoStmt)) {
|
31
|
+
if (!Section15_ControlFlow.isBeforeInCode(gotoStmt.line, gotoStmt.column, gotoStmt.label.line, gotoStmt.label.column)) {
|
32
|
+
this.logMISRAError(gotoStmt, "Back jumps using goto statements are not allowed.");
|
33
|
+
} //maybe there is a better way?
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
private r15_3_gotoBlockEnclosed($startNode: Joinpoint) {
|
38
|
+
const labelMap = new Map();
|
39
|
+
|
40
|
+
Query.searchFrom($startNode, LabelStmt).get().forEach(stmt => labelMap.set(stmt.decl.astId, stmt.astId));
|
41
|
+
|
42
|
+
for (const gotoStmt of Query.searchFrom($startNode, GotoStmt)) {
|
43
|
+
let curr = gotoStmt.getAncestor("scope");
|
44
|
+
const ancestor = gotoStmt.getAncestor("function");
|
45
|
+
let error = true;
|
46
|
+
let temp;
|
47
|
+
|
48
|
+
do {
|
49
|
+
temp = curr;
|
50
|
+
if (curr.children.map(n => n.astId).includes(labelMap.get(gotoStmt.label.astId))) {
|
51
|
+
error = false;
|
52
|
+
break;
|
53
|
+
}
|
54
|
+
curr = curr.getAncestor("scope");
|
55
|
+
} while (temp.parent.astId !== ancestor.astId);
|
56
|
+
|
57
|
+
if (error) {
|
58
|
+
this.logMISRAError(gotoStmt, `Label ${gotoStmt.label.name} is not declared in a block enclosing the goto statement.`);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
private static addBreaksToExits($startNode: Joinpoint, exits: Map<string, number>, nodes: Map<string, Joinpoint>) {
|
64
|
+
for (const goto of Query.searchFrom($startNode, Break)) {
|
65
|
+
const ancestor = goto.getAncestor("loop");
|
66
|
+
if (ancestor && exits.has(ancestor.astId)) {
|
67
|
+
exits.set(ancestor.astId, (exits.get(ancestor.astId) ?? 0) + 1);
|
68
|
+
}
|
69
|
+
else if (ancestor) {
|
70
|
+
exits.set(ancestor.astId, 1);
|
71
|
+
nodes.set(ancestor.astId, ancestor);
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
private static addGotosToExits($startNode: Joinpoint, exits: Map<string, number>, nodes: Map<string, Joinpoint>, labels: Map<string, Joinpoint>) {
|
77
|
+
for (const goto of Query.searchFrom($startNode, GotoStmt)) {
|
78
|
+
let ancestor = goto.getAncestor("loop");
|
79
|
+
const labelAncestor = labels.get(goto.label.astId)?.getAncestor("loop");
|
80
|
+
|
81
|
+
while (ancestor) {
|
82
|
+
if (labelAncestor?.astId === ancestor.astId) break;
|
83
|
+
|
84
|
+
|
85
|
+
if (ancestor && exits.has(ancestor.astId)) {
|
86
|
+
exits.set(ancestor.astId, (exits.get(ancestor.astId) ?? 0) + 1);
|
87
|
+
}
|
88
|
+
else if (ancestor) {
|
89
|
+
exits.set(ancestor.astId, 1);
|
90
|
+
nodes.set(ancestor.astId, ancestor);
|
91
|
+
}
|
92
|
+
|
93
|
+
ancestor = ancestor.getAncestor("loop");
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
private r15_4_loopSingleBreak($startNode: Joinpoint) {
|
99
|
+
const labelMap = new Map();
|
100
|
+
|
101
|
+
Query.searchFrom($startNode, LabelStmt).get().forEach(stmt => labelMap.set(stmt.decl.astId, stmt));
|
102
|
+
|
103
|
+
const exits = new Map<string, number>();
|
104
|
+
const nodes = new Map<string, Joinpoint>();
|
105
|
+
Section15_ControlFlow.addBreaksToExits($startNode, exits, nodes);
|
106
|
+
Section15_ControlFlow.addGotosToExits($startNode, exits, nodes, labelMap);
|
107
|
+
|
108
|
+
exits.forEach((v, k, m) => {
|
109
|
+
if (v > 1) {
|
110
|
+
this.logMISRAError(nodes.get(k) as Joinpoint, "Loops should have at most one break/goto statement.")
|
111
|
+
}
|
112
|
+
}, this);
|
113
|
+
}
|
114
|
+
}
|
@@ -0,0 +1,154 @@
|
|
1
|
+
import Query from "@specs-feup/lara/api/weaver/Query.js";
|
2
|
+
import { Program, FileJp, Switch, Break, Case, Joinpoint, BuiltinType, BoolLiteral, Expression } from "@specs-feup/clava/api/Joinpoints.js";
|
3
|
+
import MISRAAnalyser from "../MISRAAnalyser.js";
|
4
|
+
import Fix from "@specs-feup/clava/api/clava/analysis/Fix.js";
|
5
|
+
import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
|
6
|
+
|
7
|
+
export default class Section16_SwitchStatements extends MISRAAnalyser {
|
8
|
+
ruleMapper: Map<number, (jp: Program | FileJp) => void>;
|
9
|
+
|
10
|
+
constructor(rules: number[]) {
|
11
|
+
super(rules);
|
12
|
+
this.ruleMapper = new Map([
|
13
|
+
[1, this.r16_1_16_3_wellFormedSwitch.bind(this)],
|
14
|
+
[2, this.r16_2_topLevelSwitchLabels.bind(this)],
|
15
|
+
[4, this.r16_4_switchHasDefault.bind(this)],
|
16
|
+
[5, this.r16_5_defaultFirstOrLast.bind(this)],
|
17
|
+
[7, this.r16_7_noEssentialBooleanInSwitch.bind(this)]
|
18
|
+
]);
|
19
|
+
}
|
20
|
+
|
21
|
+
private r16_1_16_3_wellFormedSwitch($startNode: Joinpoint) {
|
22
|
+
for (const switchStmt of Query.searchFrom($startNode, Switch)) {
|
23
|
+
|
24
|
+
let foundStmt = false;
|
25
|
+
let first = true;
|
26
|
+
for (const child of switchStmt.children[1].children) {
|
27
|
+
if (child instanceof Break) {;
|
28
|
+
foundStmt = false;
|
29
|
+
}
|
30
|
+
else if (child instanceof Case) {
|
31
|
+
first = false;
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
foundStmt = true;
|
35
|
+
}
|
36
|
+
|
37
|
+
if (foundStmt && child.instanceOf("case")) {
|
38
|
+
this.logMISRAError(child, `A break is missing before ${child.code}`);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
if (!switchStmt.children[1].lastChild.instanceOf("break")) {
|
42
|
+
this.logMISRAError(switchStmt.children[1].lastChild, "A break is missing at the end of the switch statement.");
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
private r16_2_topLevelSwitchLabels($startNode: Joinpoint) {
|
48
|
+
Query.searchFrom($startNode, Case).get().forEach(caseLabel => {
|
49
|
+
if (!caseLabel.currentRegion.instanceOf("switch")) {
|
50
|
+
this.logMISRAError(caseLabel, "A switch label can only be used if its enclosing compound statement is the switch statement itself.")
|
51
|
+
}
|
52
|
+
}, this);
|
53
|
+
}
|
54
|
+
|
55
|
+
private r16_4_switchHasDefault($startNode: Joinpoint) {
|
56
|
+
Query.searchFrom($startNode, Switch, {hasDefaultCase: false}).get().forEach(sw => this.logMISRAError(sw, "Switch statement is missing a default case."), this);
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
private r16_5_defaultFirstOrLast($startNode: Joinpoint) {
|
61
|
+
Query.searchFrom($startNode, Switch).get().forEach(switchStmt => {
|
62
|
+
for (let i = 0; i < switchStmt.cases.length; i++) {
|
63
|
+
if (switchStmt.cases[i].isDefault && (i == 0 || i == switchStmt.cases.length)) {
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
else if (switchStmt.cases[i].isDefault) {
|
67
|
+
this.logMISRAError(switchStmt, "The default case of a switch statement must be the first or last label.");
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}, this);
|
72
|
+
}
|
73
|
+
|
74
|
+
private r16_6_noTwoClauses($startNode: Joinpoint) {
|
75
|
+
Query.searchFrom($startNode, Switch).get().forEach(switchStmt => {
|
76
|
+
let clauses = 0;
|
77
|
+
let foundStmt = false;
|
78
|
+
for (const child of switchStmt.children[1].children) {
|
79
|
+
if (child instanceof Case && foundStmt) {
|
80
|
+
clauses++;
|
81
|
+
foundStmt = false;
|
82
|
+
}
|
83
|
+
else if (child instanceof Break) {
|
84
|
+
clauses++;
|
85
|
+
foundStmt = false;
|
86
|
+
}
|
87
|
+
else {
|
88
|
+
foundStmt = true;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
if (clauses == 2) {
|
93
|
+
this.logMISRAError(switchStmt, "Switch statements should have more than two clauses.", new Fix(
|
94
|
+
switchStmt,
|
95
|
+
(switchStmt: Joinpoint) => {
|
96
|
+
let firstClauseExpr: Expression;
|
97
|
+
let secondClauseExpr: Expression;
|
98
|
+
let firstClause: Joinpoint[] = [];
|
99
|
+
let secondClause: Joinpoint[] = []
|
100
|
+
for (const child of switchStmt.children[1].children) {
|
101
|
+
|
102
|
+
}
|
103
|
+
}
|
104
|
+
));
|
105
|
+
}
|
106
|
+
}, this);
|
107
|
+
}
|
108
|
+
|
109
|
+
private r16_7_noEssentialBooleanInSwitch($startNode: Joinpoint) { //is this the best way?
|
110
|
+
Query.searchFrom($startNode, Switch).get().forEach(switchStmt => {
|
111
|
+
if (switchStmt.condition.type instanceof BuiltinType && switchStmt.condition.type.builtinKind === "Bool") {
|
112
|
+
this.logMISRAError(switchStmt, `Switch statement controlling expression ${switchStmt.condition.code} must not have essentially boolean type.`, new Fix(
|
113
|
+
switchStmt,
|
114
|
+
(switchStmt) => {
|
115
|
+
const trueClause: Joinpoint[] = [];
|
116
|
+
const falseClause: Joinpoint[] = [];
|
117
|
+
let inTrue: boolean = false;
|
118
|
+
let inFalse: boolean = false;
|
119
|
+
for (const child of switchStmt.children[1].children) {
|
120
|
+
if (child instanceof Case && child.values.length == 1 && child.values[0].children[0] instanceof BoolLiteral && child.values[0].children[0].value) {
|
121
|
+
inTrue = true;
|
122
|
+
inFalse = false;
|
123
|
+
}
|
124
|
+
else if (child instanceof Case && child.values.length == 1 && child.values[0].children[0] instanceof BoolLiteral && !child.values[0].children[0].value) {
|
125
|
+
inFalse = true;
|
126
|
+
inTrue = false;
|
127
|
+
}
|
128
|
+
else if (child instanceof Break) {
|
129
|
+
inTrue = false;
|
130
|
+
inFalse = false;
|
131
|
+
}
|
132
|
+
|
133
|
+
if (inTrue && !(child instanceof Case)) {
|
134
|
+
trueClause.push(child.deepCopy());
|
135
|
+
}
|
136
|
+
else if (inFalse && !(child instanceof Case)) {
|
137
|
+
falseClause.push(child.deepCopy());
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
const ifStmt = ClavaJoinPoints.ifStmt(
|
142
|
+
(switchStmt as Switch).condition,
|
143
|
+
ClavaJoinPoints.scope(...trueClause),
|
144
|
+
ClavaJoinPoints.scope(...falseClause)
|
145
|
+
);
|
146
|
+
|
147
|
+
switchStmt.replaceWith(ifStmt);
|
148
|
+
}
|
149
|
+
));
|
150
|
+
}
|
151
|
+
}, this);
|
152
|
+
}
|
153
|
+
|
154
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { Program, FileJp, Joinpoint, Include, Call, BuiltinType, ExprStmt } from "@specs-feup/clava/api/Joinpoints.js";
|
2
|
+
import MISRAAnalyser from "../MISRAAnalyser.js";
|
3
|
+
import Query from "@specs-feup/lara/api/weaver/Query.js";
|
4
|
+
import Fix from "@specs-feup/clava/api/clava/analysis/Fix.js";
|
5
|
+
import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
|
6
|
+
|
7
|
+
export default class Section17_Functions extends MISRAAnalyser {
|
8
|
+
protected ruleMapper: Map<number, (jp: Program | FileJp) => void>;
|
9
|
+
|
10
|
+
constructor(rules: number[]) {
|
11
|
+
super(rules);
|
12
|
+
this.ruleMapper = new Map([
|
13
|
+
[1, this.r17_1_noStdargUsage.bind(this)],
|
14
|
+
[7, this.r17_7_returnValuesAreUsed.bind(this)]
|
15
|
+
]);
|
16
|
+
}
|
17
|
+
|
18
|
+
private r17_1_noStdargUsage($startNode: Joinpoint) {
|
19
|
+
Query.searchFrom($startNode, Include, {name: "stdarg.h", isAngled: true}).get().forEach(include => this.logMISRAError(include, "Use of <stdarg.h> is not allowed."), this);
|
20
|
+
}
|
21
|
+
|
22
|
+
private r17_7_returnValuesAreUsed($startNode: Joinpoint) {
|
23
|
+
Query.searchFrom($startNode, Call, {returnType: (type) => !(type instanceof BuiltinType && type.isVoid)}).get().forEach(call => {
|
24
|
+
console.log(call.parent);
|
25
|
+
if (call.parent instanceof ExprStmt) {
|
26
|
+
this.logMISRAError(call, `Return value of ${call.signature} must be used. It can be discarded with an explicit cast to void.`, new Fix(call, ($jp) => {
|
27
|
+
const newJp = ClavaJoinPoints.cStyleCast(ClavaJoinPoints.type("void"), $jp.deepCopy() as Call);
|
28
|
+
$jp.replaceWith(newJp);
|
29
|
+
}));
|
30
|
+
}
|
31
|
+
}, this);
|
32
|
+
}
|
33
|
+
}
|