@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.
Files changed (65) hide show
  1. package/CxxSources/lib.cpp +3 -0
  2. package/CxxSources/lib.h +8 -0
  3. package/CxxSources/main.cpp +40 -0
  4. package/README.md +32 -0
  5. package/TODO.md +1 -0
  6. package/consumer_order.txt +2 -0
  7. package/enum_integer_type.txt +0 -0
  8. package/is_temporary.txt +0 -0
  9. package/jest.config.js +25 -0
  10. package/omp.txt +0 -0
  11. package/package.json +23 -0
  12. package/src/foo.ts +6 -0
  13. package/src/main.ts +36 -0
  14. package/src/misra/MISRAAnalyser.ts +43 -0
  15. package/src/misra/MISRAPass.ts +85 -0
  16. package/src/misra/MISRAPassResult.ts +20 -0
  17. package/src/misra/MISRAReporter.ts +57 -0
  18. package/src/misra/passes/S10_EssentialTypePass.ts +395 -0
  19. package/src/misra/passes/S12_ExpressionPass.ts +86 -0
  20. package/src/misra/passes/S13_SideEffectPass.ts +121 -0
  21. package/src/misra/passes/S15_ControlFlowPass.ts +108 -0
  22. package/src/misra/passes/S16_SwitchStatementPass.ts +168 -0
  23. package/src/misra/passes/S17_FunctionPass.ts +43 -0
  24. package/src/misra/passes/S18_PointersArraysPass.ts +126 -0
  25. package/src/misra/passes/S19_OverlappingStoragePass.ts +27 -0
  26. package/src/misra/passes/S21_StandardLibPass.ts +92 -0
  27. package/src/misra/passes/S3_CommentPass.ts +40 -0
  28. package/src/misra/passes/S5_IdentifierPass.ts +66 -0
  29. package/src/misra/passes/S6_TypePass.ts +32 -0
  30. package/src/misra/passes/S7_LiteralsConstantsPass.ts +80 -0
  31. package/src/misra/passes/S8_DeclDefPass.ts +138 -0
  32. package/src/misra/sections/Section10_EssentialTypeModel.ts +377 -0
  33. package/src/misra/sections/Section11_PointerTypeConversions.ts +103 -0
  34. package/src/misra/sections/Section12_Expressions.ts +80 -0
  35. package/src/misra/sections/Section13_SideEffects.ts +100 -0
  36. package/src/misra/sections/Section14_ControlStmtExprs.ts +27 -0
  37. package/src/misra/sections/Section15_ControlFlow.ts +114 -0
  38. package/src/misra/sections/Section16_SwitchStatements.ts +154 -0
  39. package/src/misra/sections/Section17_Functions.ts +33 -0
  40. package/src/misra/sections/Section18_PointersAndArrays.ts +108 -0
  41. package/src/misra/sections/Section19_OverlappingStorage.ts +18 -0
  42. package/src/misra/sections/Section20_PreprocessingDirectives.ts +22 -0
  43. package/src/misra/sections/Section21_StandardLibraries.ts +65 -0
  44. package/src/misra/sections/Section2_UnusedCode.ts +47 -0
  45. package/src/misra/sections/Section3_Comments.ts +23 -0
  46. package/src/misra/sections/Section5_Identifiers.ts +149 -0
  47. package/src/misra/sections/Section6_Types.ts +26 -0
  48. package/src/misra/sections/Section7_LiteralsConstants.ts +76 -0
  49. package/src/misra/sections/Section8_DeclarationsDefinitions.ts +133 -0
  50. package/src/misra/tests/S10_EssentialTypes.test.ts +253 -0
  51. package/src/misra/tests/S12_Expressions.test.ts +43 -0
  52. package/src/misra/tests/S13_SideEffects.test.ts +77 -0
  53. package/src/misra/tests/S15_ControlFlow.test.ts +144 -0
  54. package/src/misra/tests/S16_SwitchStatements.test.ts +164 -0
  55. package/src/misra/tests/S17_Functions.test.ts +46 -0
  56. package/src/misra/tests/S18_PointersArrays.test.ts +167 -0
  57. package/src/misra/tests/S19_OverlappingStorage.test.ts +38 -0
  58. package/src/misra/tests/S3_Comments.test.ts +36 -0
  59. package/src/misra/tests/S6_Types.test.ts +36 -0
  60. package/src/misra/tests/S7_LiteralsConstants.test.ts +48 -0
  61. package/src/misra/tests/utils.ts +47 -0
  62. package/tsconfig.jest.json +5 -0
  63. package/tsconfig.json +18 -0
  64. package/typedoc.config.js +6 -0
  65. package/types_with_templates.txt +0 -0
@@ -0,0 +1,395 @@
1
+ import { LaraJoinPoint } from "@specs-feup/lara/api/LaraJoinPoint.js";
2
+ import MISRAPass from "../MISRAPass.js";
3
+ import { PreprocessingReqs } from "../MISRAReporter.js";
4
+ import { BinaryOp, BuiltinType, Call, Cast, EnumType, Expression, FunctionJp, IntLiteral, Joinpoint, ParenExpr, QualType, ReturnStmt, TernaryOp, Type, UnaryOp, Vardecl } from "@specs-feup/clava/api/Joinpoints.js";
5
+ import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
6
+ import Fix from "@specs-feup/clava/api/clava/analysis/Fix.js";
7
+
8
+ export enum EssentialTypes {
9
+ UNSIGNED = "unsigned",
10
+ CHAR = "char",
11
+ SIGNED = "signed",
12
+ ENUM = "enum",
13
+ FLOAT = "float",
14
+ BOOL = "bool",
15
+ UNKOWN = "unkown"
16
+ };
17
+
18
+ export interface EssentialType {
19
+ category: EssentialTypes,
20
+ enumName?: string
21
+ }
22
+
23
+ export default class S10_EssentialTypePass extends MISRAPass {
24
+ protected _preprocessingReqs: PreprocessingReqs[] = [];
25
+
26
+ initRuleMapper(): void {
27
+ this._ruleMapper = new Map([
28
+ [1, this.r10_1_appropriateEssentialOperands.bind(this)],
29
+ [2, this.r10_2_appropriateCharOperands.bind(this)],
30
+ [3, this.r10_3_noInvalidAssignments.bind(this)],
31
+ [5, this.r10_5_noInvalidCasts.bind(this)],
32
+ [6, this.r10_6_noWiderCompositeExprAssignments.bind(this)],
33
+ [8, this.r10_8_noWiderCompositeCasts.bind(this)]
34
+ ]);
35
+ }
36
+
37
+ matchJoinpoint($jp: LaraJoinPoint): boolean {
38
+ return $jp instanceof Expression || $jp instanceof Vardecl || $jp instanceof ReturnStmt;
39
+ }
40
+
41
+ static getEssentialType(bType: Type): EssentialType {
42
+ let type = bType.desugarAll;
43
+ while (type instanceof QualType) {
44
+ type = type.unqualifiedType.desugarAll;
45
+ }
46
+
47
+ if (type instanceof BuiltinType) {
48
+ if (type.builtinKind === "Bool") {
49
+ return {category: EssentialTypes.BOOL};
50
+ }
51
+ else if (type.builtinKind === "Char_S") {
52
+ return {category: EssentialTypes.CHAR};
53
+ }
54
+ else if (type.isInteger && type.isSigned === true) {
55
+ return {category: EssentialTypes.SIGNED};
56
+ }
57
+ else if (type.isInteger && type.isSigned === false) {
58
+ return {category: EssentialTypes.UNSIGNED};
59
+ }
60
+ else if (type.isFloat) {
61
+ return {category: EssentialTypes.FLOAT};
62
+ }
63
+ }
64
+ else if (type instanceof EnumType) {
65
+ return type.name === "" ? {category: EssentialTypes.SIGNED} : {category: EssentialTypes.ENUM, enumName: type.name};
66
+ }
67
+
68
+ return {category: EssentialTypes.UNKOWN};
69
+ }
70
+
71
+ static isInteger($et: EssentialTypes): boolean {
72
+ return $et === EssentialTypes.SIGNED || $et === EssentialTypes.UNSIGNED;
73
+ }
74
+
75
+ static getExprEssentialType($expr: Expression): EssentialType {
76
+ if ($expr instanceof BinaryOp) {
77
+ if ($expr.kind === "add") {
78
+ if ((this.getExprEssentialType($expr.left).category === EssentialTypes.CHAR && this.isInteger(this.getExprEssentialType($expr.right).category))
79
+ || (this.getExprEssentialType($expr.right).category === EssentialTypes.CHAR && this.isInteger(this.getExprEssentialType($expr.left).category))) {
80
+ return {category: EssentialTypes.CHAR};
81
+ }
82
+ }
83
+ else if ($expr.kind === "sub") {
84
+ if (this.getExprEssentialType($expr.left).category === EssentialTypes.CHAR) {
85
+ if (this.getExprEssentialType($expr.right).category === EssentialTypes.CHAR) {
86
+ return {category: EssentialTypes.CHAR};
87
+ }
88
+ }
89
+ }
90
+ }
91
+ return this.getEssentialType($expr.type);
92
+ }
93
+
94
+ private restrictOperand($expr: Expression, $restrictedType: EssentialTypes, $baseExpr: Expression, $castTo?: BuiltinType) {
95
+ const et = S10_EssentialTypePass.getExprEssentialType($expr);
96
+ if (et.category === $restrictedType) {
97
+ const fix = $castTo ? new Fix($expr, ($jp) => {
98
+ $jp.replaceWith(ClavaJoinPoints.cStyleCast($castTo, $jp as Expression));
99
+ }) : undefined;
100
+ this.logMISRAError(`Operand ${$expr.code} of expression ${$baseExpr.code} must not have essentially ${et.category} type.`, fix);
101
+ }
102
+ }
103
+
104
+ private restrictOperandList($expr: Expression, $restrictedTypes: EssentialTypes[], $baseExpr: Expression, $castTo?: BuiltinType) {
105
+ for (const type of $restrictedTypes) {
106
+ this.restrictOperand($expr, type, $baseExpr, $castTo);
107
+ }
108
+ }
109
+
110
+ private r10_1_appropriateEssentialOperands($startNode: Joinpoint) { //missing exception and compound operators
111
+ if ($startNode instanceof TernaryOp) {
112
+ this.restrictOperandList($startNode.cond, [EssentialTypes.CHAR, EssentialTypes.ENUM, EssentialTypes.FLOAT, EssentialTypes.SIGNED, EssentialTypes.UNSIGNED], $startNode);
113
+ }
114
+ else if ($startNode instanceof BinaryOp) {
115
+ switch ($startNode.kind) {
116
+ case "shl":
117
+ case "shr":
118
+ if (!($startNode.right instanceof IntLiteral)) this.restrictOperand($startNode.right, EssentialTypes.SIGNED, $startNode);
119
+ case "and":
120
+ case "or":
121
+ case "x_or":
122
+ this.restrictOperand($startNode.left, EssentialTypes.SIGNED, $startNode);
123
+ if ($startNode.kind !== "shl" && $startNode.kind !== "shr") this.restrictOperand($startNode.right, EssentialTypes.SIGNED, $startNode);
124
+ case "rem":
125
+ this.restrictOperand($startNode.left, EssentialTypes.FLOAT, $startNode);
126
+ this.restrictOperand($startNode.right, EssentialTypes.FLOAT, $startNode);
127
+ case "mul":
128
+ case "div":
129
+ this.restrictOperand($startNode.left, EssentialTypes.CHAR, $startNode);
130
+ this.restrictOperand($startNode.right, EssentialTypes.CHAR, $startNode);
131
+ case "add":
132
+ case "sub":
133
+ this.restrictOperand($startNode.left, EssentialTypes.ENUM, $startNode);
134
+ this.restrictOperand($startNode.right, EssentialTypes.ENUM, $startNode);
135
+ case "le":
136
+ case "ge":
137
+ case "lt":
138
+ case "gt":
139
+ this.restrictOperand($startNode.left, EssentialTypes.BOOL, $startNode);
140
+ this.restrictOperand($startNode.right, EssentialTypes.BOOL, $startNode);
141
+ break;
142
+ case "l_and":
143
+ case "l_or":
144
+ this.restrictOperandList($startNode.left, [EssentialTypes.ENUM, EssentialTypes.CHAR, EssentialTypes.SIGNED, EssentialTypes.UNSIGNED, EssentialTypes.FLOAT], $startNode);
145
+ this.restrictOperandList($startNode.right, [EssentialTypes.ENUM, EssentialTypes.CHAR, EssentialTypes.SIGNED, EssentialTypes.UNSIGNED, EssentialTypes.FLOAT], $startNode);
146
+ break;
147
+ }
148
+ }
149
+ else if ($startNode instanceof UnaryOp) {
150
+ switch ($startNode.kind) {
151
+ case "minus":
152
+ this.restrictOperand($startNode.operand, EssentialTypes.UNSIGNED, $startNode);
153
+ case "plus":
154
+ this.restrictOperandList($startNode.operand, [EssentialTypes.BOOL, EssentialTypes.CHAR, EssentialTypes.ENUM], $startNode);
155
+ break;
156
+ case "l_not":
157
+ this.restrictOperandList($startNode.operand, [EssentialTypes.ENUM, EssentialTypes.CHAR, EssentialTypes.SIGNED, EssentialTypes.UNSIGNED, EssentialTypes.FLOAT], $startNode);
158
+ break;
159
+ case "not":
160
+ this.restrictOperandList($startNode.operand, [EssentialTypes.BOOL, EssentialTypes.CHAR, EssentialTypes.ENUM, EssentialTypes.FLOAT, EssentialTypes.SIGNED], $startNode);
161
+ break;
162
+ }
163
+ }
164
+ }
165
+
166
+ private r10_2_appropriateCharOperands($startNode: Joinpoint) {
167
+ if (!($startNode instanceof BinaryOp)) return;
168
+
169
+ if ($startNode.kind === "add") {
170
+ if (S10_EssentialTypePass.getExprEssentialType($startNode.left).category === EssentialTypes.CHAR && S10_EssentialTypePass.getExprEssentialType($startNode.right).category === EssentialTypes.CHAR) {
171
+ this.logMISRAError(`Both operands of addition ${$startNode.code} have essentially character type.`);
172
+ return;
173
+ }
174
+
175
+ let otherType;
176
+ if (S10_EssentialTypePass.getExprEssentialType($startNode.left).category === EssentialTypes.CHAR) {
177
+ otherType = S10_EssentialTypePass.getExprEssentialType($startNode.right);
178
+ }
179
+ else if (S10_EssentialTypePass.getExprEssentialType($startNode.right).category === EssentialTypes.CHAR) {
180
+ otherType = S10_EssentialTypePass.getExprEssentialType($startNode.left);
181
+ }
182
+
183
+ if (otherType && !S10_EssentialTypePass.isInteger(otherType.category)) {
184
+ this.logMISRAError(`One operand of addition ${$startNode.code} has essentially character type, so the other one must have either essentially signed or unsigned type.`);
185
+ return;
186
+ }
187
+ }
188
+ else if ($startNode.kind === "sub") {
189
+ if (S10_EssentialTypePass.getExprEssentialType($startNode.left).category === EssentialTypes.CHAR) {
190
+ const rightType = S10_EssentialTypePass.getExprEssentialType($startNode.right);
191
+ if (!([EssentialTypes.CHAR, EssentialTypes.SIGNED, EssentialTypes.UNKOWN].some(et => et === rightType.category))) {
192
+ this.logMISRAError(`Left operand of subtraction ${$startNode.code} has essentially character type, so the RHS must be essentially signed, unsigned, or char.`);
193
+ return;
194
+ }
195
+ }
196
+ else if (S10_EssentialTypePass.getExprEssentialType($startNode.right).category === EssentialTypes.CHAR) {
197
+ this.logMISRAError(`Right operand of subtraction ${$startNode.code} can only be of essentially character type if the LHS is too.`);
198
+ return;
199
+ }
200
+ }
201
+ }
202
+
203
+ private static isAssignable($t1: EssentialType, $t2: EssentialType, $rhs: Expression) {
204
+ if ($t1.category === EssentialTypes.UNSIGNED && $rhs instanceof IntLiteral) {
205
+ return true;
206
+ }
207
+ else if ($t1.category !== $t2.category) {
208
+ return false;
209
+ }
210
+ else if ($t1.category === EssentialTypes.ENUM && $t1.enumName === $t2.enumName) {
211
+ return false;
212
+ }
213
+ else return true;
214
+ }
215
+
216
+ private r10_3_noInvalidAssignments($startNode: Joinpoint) { //enum value not calculated, compound assignments, return stmt
217
+ if ($startNode instanceof BinaryOp && $startNode.kind === "assign") {
218
+ if (!S10_EssentialTypePass.isAssignable(S10_EssentialTypePass.getExprEssentialType($startNode.left), S10_EssentialTypePass.getExprEssentialType($startNode.right), $startNode.right)) {
219
+ this.logMISRAError(`Value ${$startNode.right.code} cannot be assigned to ${$startNode.left.code}, since it has a different essential type category.`);
220
+ }
221
+ else if ($startNode.left.bitWidth < $startNode.right.bitWidth) {
222
+ this.logMISRAError(`Value ${$startNode.right.code} cannot be assigned to ${$startNode.left.code} since it has a narrower type.`);
223
+ }
224
+ }
225
+ else if ($startNode instanceof Vardecl && $startNode.hasInit) {
226
+ if (!S10_EssentialTypePass.isAssignable(S10_EssentialTypePass.getEssentialType($startNode.type), S10_EssentialTypePass.getExprEssentialType($startNode.init), $startNode.init)) {
227
+ this.logMISRAError(`Value ${$startNode.init.code} cannot be initialized in ${$startNode.code}, since it has a different essential type category.`);
228
+ }
229
+ else if ($startNode.bitWidth < $startNode.init.bitWidth) {
230
+ this.logMISRAError(`Value ${$startNode.init.code} cannot be initialized im ${$startNode.code} since it has a narrower type.`);
231
+ }
232
+ }
233
+ else if ($startNode instanceof ReturnStmt && $startNode.returnExpr) {
234
+ const fun = $startNode.getAncestor("function") as FunctionJp;
235
+ if (!S10_EssentialTypePass.isAssignable(S10_EssentialTypePass.getExprEssentialType($startNode.returnExpr), S10_EssentialTypePass.getEssentialType(fun.returnType), $startNode.returnExpr)) {
236
+ this.logMISRAError(`Value ${$startNode.returnExpr.code} cannot be returned by ${fun.signature}, since it has a different essential type category.`);
237
+ }
238
+ else if (fun.bitWidth < $startNode.returnExpr.bitWidth) {
239
+ this.logMISRAError(`Value ${$startNode.returnExpr.code} cannot be returned by ${fun.signature} since it has a narrower type.`);
240
+ }
241
+ }
242
+ else if ($startNode instanceof Call) {
243
+ const funParams = $startNode.directCallee.params;
244
+ for (let i = 0; i < funParams.length; i++) {
245
+ if (!S10_EssentialTypePass.isAssignable(S10_EssentialTypePass.getEssentialType(funParams[i].type), S10_EssentialTypePass.getEssentialType($startNode.argList[i].type), $startNode.argList[i])) {
246
+ this.logMISRAError(`Value ${$startNode.argList[i].code} cannot be assigned to parameter ${funParams[i].code}, since it has a different essential type category.`);
247
+ }
248
+ else if (funParams[i].bitWidth < $startNode.argList[i].bitWidth) {
249
+ this.logMISRAError(`Value ${$startNode.argList[i].code} cannot be assigned to parameter ${funParams[i].code}, since it has a narrower type.`);
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ private static checkBoolSource(subExpr: Expression) {
256
+ if (S10_EssentialTypePass.getExprEssentialType(subExpr).category === EssentialTypes.BOOL) {
257
+ return true;
258
+ }
259
+ else if (subExpr instanceof IntLiteral && (Number(subExpr.value) === 0 || Number(subExpr.value) === 1)) {
260
+ return true;
261
+ }
262
+ else return false;
263
+ }
264
+
265
+ private r10_5_noInvalidCasts($startNode: Joinpoint) {//TODO: add char rules
266
+ if (!($startNode instanceof Cast)) return;
267
+
268
+ const subExpr = $startNode.subExpr;
269
+ const toType = $startNode.toType.desugarAll;
270
+
271
+ const toEssentialType = S10_EssentialTypePass.getEssentialType(toType);
272
+ const fromEssentialType = S10_EssentialTypePass.getExprEssentialType(subExpr);
273
+
274
+ if (toEssentialType.category === EssentialTypes.BOOL && !S10_EssentialTypePass.checkBoolSource($startNode.subExpr)) {
275
+ this.logMISRAError("Only essentially boolean values, or the integer constants 0 or 1, may be cast to an essentially boolean type.");
276
+ }
277
+ else if ((toEssentialType.category === EssentialTypes.ENUM && fromEssentialType.category === EssentialTypes.ENUM && toEssentialType.enumName !== fromEssentialType.enumName)
278
+ || (toEssentialType.category === EssentialTypes.ENUM && fromEssentialType.category !== EssentialTypes.ENUM)) {
279
+ this.logMISRAError("Only essentially enum values of the same enum may be cast to an essentially enum type.");
280
+ }
281
+ else if (toEssentialType.category === EssentialTypes.SIGNED && fromEssentialType.category === EssentialTypes.BOOL) {
282
+ this.logMISRAError("Essentially boolean values should not be cast to an essentially signed type.");
283
+ }
284
+ else if (toEssentialType.category === EssentialTypes.UNSIGNED && fromEssentialType.category === EssentialTypes.BOOL) {
285
+ this.logMISRAError("Essentially boolean values should not be cast to an essentially unsigned type.");
286
+ }
287
+ else if (toEssentialType.category === EssentialTypes.FLOAT && fromEssentialType.category === EssentialTypes.BOOL) {
288
+ this.logMISRAError("Essentially boolean values should not be cast to an essentially floating type.");
289
+ }
290
+ }
291
+
292
+ private static isCompositeBinaryExpr($op: BinaryOp) {
293
+ return /(add)|(sub)|(mul)|(div)|(rem)|(shl)|(shr)|(l_and)|(l_or)|(xor)/.test($op.kind);
294
+ }
295
+
296
+ private static isCompositeExpr($expr: Expression): Expression | undefined {
297
+ if ($expr instanceof ParenExpr) {
298
+ return this.isCompositeExpr($expr.subExpr);
299
+ }
300
+ else if ($expr instanceof BinaryOp) {
301
+ if (this.isCompositeBinaryExpr($expr)) {
302
+ return $expr;
303
+ }
304
+ }
305
+ else if ($expr instanceof TernaryOp) {
306
+ if (this.isCompositeExpr($expr.trueExpr) || this.isCompositeExpr($expr.falseExpr)) {
307
+ return $expr;
308
+ }
309
+ }
310
+ else return undefined;
311
+ }
312
+
313
+ private static compositeExprWidth($op: Expression): number {
314
+ if ($op instanceof BinaryOp && S10_EssentialTypePass.isCompositeBinaryExpr($op)) {
315
+ const leftW = S10_EssentialTypePass.compositeExprWidth($op.left);
316
+ const rightW = S10_EssentialTypePass.compositeExprWidth($op.right);
317
+ return Math.max(leftW, rightW);
318
+ }
319
+ else if ($op instanceof TernaryOp) {
320
+ const secondW = S10_EssentialTypePass.compositeExprWidth($op.trueExpr);
321
+ const thirdW = S10_EssentialTypePass.compositeExprWidth($op.falseExpr);
322
+ return Math.min(secondW, thirdW);
323
+ }
324
+ else if ($op instanceof ParenExpr) {
325
+ return this.compositeExprWidth($op.subExpr);
326
+ }
327
+ else return $op.bitWidth;
328
+ }
329
+
330
+ private static transformBinaryOp($expr: BinaryOp, $type: Type) {
331
+ $expr.left.replaceWith(ClavaJoinPoints.cStyleCast($type, $expr.left));
332
+ }
333
+
334
+ private static transformTernaryOp($expr: TernaryOp, $type: Type, $bitWidth: number) {
335
+ const trueExpr = this.isCompositeExpr($expr.trueExpr);
336
+ const falseExpr = this.isCompositeExpr($expr.falseExpr);
337
+ if (trueExpr && this.compositeExprWidth(trueExpr) < $bitWidth) {
338
+ if (trueExpr instanceof TernaryOp) this.transformTernaryOp(trueExpr, $type, $bitWidth);
339
+ else if (trueExpr instanceof BinaryOp) this.transformBinaryOp(trueExpr, $type);
340
+ }
341
+
342
+ if (falseExpr && this.compositeExprWidth(falseExpr) < $type.bitWidth) {
343
+ if (falseExpr instanceof TernaryOp) this.transformTernaryOp(falseExpr, $type, $bitWidth);
344
+ else if (falseExpr instanceof BinaryOp) this.transformBinaryOp(falseExpr, $type);
345
+ }
346
+ }
347
+
348
+ private r10_6_noWiderCompositeExprAssignments($startNode: Joinpoint) { //unfinished for rets and params
349
+ if (!($startNode instanceof Expression)) return;
350
+
351
+ const compositeExpr = S10_EssentialTypePass.isCompositeExpr($startNode);
352
+ if (compositeExpr) {
353
+ const parent = $startNode.parent;
354
+ if (parent instanceof BinaryOp && parent.kind === "assign") {
355
+ if (S10_EssentialTypePass.compositeExprWidth(compositeExpr) < parent.left.bitWidth) {
356
+ this.logMISRAError("A composite expression must not be assigned to a value with wider type.", new Fix(compositeExpr, op => {
357
+ if (op instanceof BinaryOp) {
358
+ S10_EssentialTypePass.transformBinaryOp(op, op.parent.type);
359
+ }
360
+ else if (op instanceof TernaryOp) {
361
+ op.replaceWith(ClavaJoinPoints.cStyleCast(op.parent.type, op));
362
+ }
363
+ }));
364
+ }
365
+ }
366
+ }
367
+ }
368
+
369
+ private r10_8_noWiderCompositeCasts($startNode: Joinpoint) {
370
+ if (!($startNode instanceof Cast)) return;
371
+
372
+ const compositeExpr = S10_EssentialTypePass.isCompositeExpr($startNode.subExpr);
373
+ if (compositeExpr) {
374
+ if (S10_EssentialTypePass.getEssentialType($startNode.fromType).category !== S10_EssentialTypePass.getEssentialType($startNode.toType).category) {
375
+ this.logMISRAError(`Composite expression ${$startNode.subExpr.code} cannot be cast to ${$startNode.toType.code}, since it has a different essential type category.`);
376
+ }
377
+ else if ($startNode.bitWidth > S10_EssentialTypePass.compositeExprWidth(compositeExpr)) {
378
+ this.logMISRAError(`Composite expression ${$startNode.subExpr.code} cannot be cast to ${$startNode.toType.code} since it is a wider type.`, new Fix($startNode, cast => {
379
+ const castJp = cast as Cast;
380
+ const compositeExpr = S10_EssentialTypePass.isCompositeExpr(castJp.subExpr);
381
+ if (compositeExpr instanceof BinaryOp) {
382
+ compositeExpr.left.replaceWith(ClavaJoinPoints.cStyleCast(cast.type, compositeExpr.left));
383
+ cast.replaceWith(compositeExpr);
384
+ }
385
+ else if (compositeExpr instanceof TernaryOp) {
386
+ S10_EssentialTypePass.transformTernaryOp(compositeExpr, cast.type, cast.bitWidth);
387
+ }
388
+ }));
389
+ }
390
+ }
391
+ }
392
+
393
+ protected _name: string = "Essential type model";
394
+
395
+ }
@@ -0,0 +1,86 @@
1
+ import { LaraJoinPoint } from "@specs-feup/lara/api/LaraJoinPoint.js";
2
+ import MISRAPass from "../MISRAPass.js";
3
+ import { PreprocessingReqs } from "../MISRAReporter.js";
4
+ import { BinaryOp, Expression, Joinpoint, Loop, Op } from "@specs-feup/clava/api/Joinpoints.js";
5
+ import Fix from "@specs-feup/clava/api/clava/analysis/Fix.js";
6
+ import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
7
+
8
+ export default class S12_ExpressionPass extends MISRAPass {
9
+ protected _preprocessingReqs: PreprocessingReqs[] = [];
10
+
11
+ initRuleMapper(): void {
12
+ this._ruleMapper = new Map([
13
+ [1, this.r12_1_explicitPrecedence.bind(this)],
14
+ [3, this.r12_3_noCommaOperator.bind(this)]
15
+ ]);
16
+ }
17
+ matchJoinpoint($jp: LaraJoinPoint): boolean {
18
+ return $jp instanceof BinaryOp && !$jp.isAssignment;
19
+ }
20
+
21
+ private static isAdditive(op: string): boolean {
22
+ return op === "add" || op === "sub";
23
+ }
24
+
25
+ private static isMultiplicative(op: string): boolean {
26
+ return op === "mul" || op === "div" || op === "rem";
27
+ }
28
+
29
+ private static isShift(op: string): boolean {
30
+ return op === "shl" || op === "shr";
31
+ }
32
+
33
+ private static isRelational(op: string): boolean {
34
+ return op === "lt" || op === "gt" || op === "le" || op === "ge";
35
+ }
36
+
37
+ private static isEquality(op: string): boolean {
38
+ return op === "eq" || op === "ne";
39
+ }
40
+
41
+ private static isSamePrecedence(op1: string, op2: string): boolean {
42
+ if (op1 === op2) return true;
43
+
44
+ for (const fun of [this.isAdditive, this.isMultiplicative, this.isShift, this.isRelational, this.isEquality]) {
45
+ if (fun(op1) && fun(op2)) {
46
+ return true;
47
+ }
48
+ }
49
+
50
+ return false;
51
+ }
52
+
53
+ private r12_1_explicitPrecedence($startNode: Joinpoint) {
54
+ if (!($startNode instanceof BinaryOp)) return;
55
+
56
+ if (($startNode.kind === "ptr_mem_d" || $startNode.kind === "ptr_mem_i")
57
+ || (!$startNode.getAncestor("binaryOp") && !$startNode.getAncestor("ternaryOp"))
58
+ || ($startNode.parent instanceof BinaryOp && $startNode.parent.isAssignment)
59
+ || ($startNode.parent instanceof Op && S12_ExpressionPass.isSamePrecedence($startNode.kind, $startNode.parent.kind))
60
+ || ($startNode.parent.instanceOf("parenExpr"))) return;
61
+
62
+ this.logMISRAError(`Operator precedence in expression ${$startNode.code} is not explicit.`, new Fix($startNode, ($jp: Joinpoint) => {
63
+ const parenExpr = ClavaJoinPoints.parenthesis($jp as Expression);
64
+ $jp.replaceWith(parenExpr);
65
+ }));
66
+ }
67
+
68
+ private r12_3_noCommaOperator($startNode: Joinpoint) {
69
+ if (!($startNode instanceof BinaryOp && $startNode.operator === ",")) return;
70
+
71
+ const loopAncestor = $startNode.getAncestor("loop");
72
+ if (loopAncestor instanceof Loop && (loopAncestor?.step?.contains($startNode) || loopAncestor?.cond?.contains($startNode) || loopAncestor?.init?.contains($startNode))) {
73
+ console.log(`Cannot eliminate comma operator in expression ${$startNode.code} since it is at the head of a loop.`);
74
+ this.logMISRAError("Use of the comma operator is not allowed.");
75
+ }
76
+ else {
77
+ this.logMISRAError("Use of the comma operator is not allowed.", new Fix($startNode, ($jp: Joinpoint) => {
78
+ $jp.insertBefore(($jp as BinaryOp).left.stmt);
79
+ $jp.replaceWith(($jp as BinaryOp).right);
80
+ }));
81
+ }
82
+ }
83
+
84
+ protected _name: string = "Expressions";
85
+
86
+ }
@@ -0,0 +1,121 @@
1
+ import { LaraJoinPoint } from "@specs-feup/lara/api/LaraJoinPoint.js";
2
+ import MISRAPass from "../MISRAPass.js";
3
+ import { PreprocessingReqs } from "../MISRAReporter.js";
4
+ import Query from "@specs-feup/lara/api/weaver/Query.js";
5
+ import { BinaryOp, Call, ExprStmt, InitList, Joinpoint, QualType, UnaryExprOrType, UnaryOp, Vardecl, Varref } from "@specs-feup/clava/api/Joinpoints.js";
6
+ import TraversalType from "@specs-feup/lara/api/weaver/TraversalType.js";
7
+ import Fix from "@specs-feup/clava/api/clava/analysis/Fix.js";
8
+
9
+ export default class S13_SideEffectPass extends MISRAPass {
10
+ protected _preprocessingReqs: PreprocessingReqs[] = [];
11
+
12
+ initRuleMapper(): void {
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
+ [6, this.r13_6_sizeofSideEffects.bind(this)]
19
+ ]);
20
+ }
21
+
22
+ matchJoinpoint($jp: LaraJoinPoint): boolean {
23
+ return $jp instanceof InitList || $jp instanceof ExprStmt || $jp instanceof BinaryOp || $jp instanceof UnaryExprOrType;
24
+ }
25
+
26
+ private checkPotentialPersistentSideEffects<T extends Joinpoint>($startNode: T, name: string, childFun: ($jp: T) => Joinpoint) {
27
+ Query.searchFromInclusive(childFun($startNode), Varref).get().forEach(ref => {
28
+ if (ref.type instanceof QualType && ref.type.qualifiers?.includes("volatile")) {
29
+ this.logMISRAError(`${name} ${$startNode.code} contains persistent side effects: an access to volatile object ${ref.name}.`)
30
+ }
31
+ }, this);
32
+ Query.searchFromInclusive(childFun($startNode), Call).get().forEach(call => {
33
+ this.logMISRAError(`${name} ${$startNode.code} may contain persistent side effects in call to ${call.name}.`);
34
+ }, this);
35
+ Query.searchFromInclusive(childFun($startNode), UnaryOp, {kind: /(post_inc)|(post_dec)|(pre_inc)|(pre_dec)/}).get().forEach(op => { //use chain?
36
+ Query.searchFrom(op, Varref).get().forEach(ref => {
37
+ if (ref.declaration instanceof Vardecl && ref.declaration.isGlobal) {
38
+ this.logMISRAError(`${name} ${$startNode.code} may contain persistent side effects in expression ${op.code}.`)
39
+ }
40
+ });
41
+ }, this);
42
+ }
43
+
44
+ private r13_1_initListSideEffects($startNode: Joinpoint) {
45
+ if (!($startNode instanceof InitList)) return;
46
+
47
+ this.checkPotentialPersistentSideEffects<Joinpoint>($startNode, "Initializer list", jp => jp);
48
+ }
49
+
50
+ private static visitAllExprs(fun: ($jp: Joinpoint) => void, root: Joinpoint) {
51
+ let curr = root;
52
+ while (curr) {
53
+ const temp = curr;
54
+ //console.log(temp.joinPointType);
55
+ curr = curr.rightJp;
56
+ if (temp.instanceOf("expression")) {
57
+ fun(temp);
58
+ }
59
+ else S13_SideEffectPass.visitAllExprs(fun, temp.children[0]);
60
+ }
61
+ }
62
+
63
+ private checkIncrementSideEffects(exprRoot: Joinpoint) {
64
+ const jps = Query.searchFrom(exprRoot, UnaryOp, {kind: /(post_inc)|(post_dec)|(pre_inc)|(pre_dec)/}, TraversalType.POSTORDER).get();
65
+ //THE ABOVE LINE ONLY WORKS BECAUSE SEARCH FROM IS RETURNING THE ROOT, SEARCH FROM INCLUSIVE RETURNS IT TWICE
66
+ const calls = Query.searchFromInclusive(exprRoot, Call).get();
67
+ const assignments = Query.searchFromInclusive(exprRoot, BinaryOp, {isAssignment: true}).get();
68
+ if (jps.length == 0 || jps.length + calls.length + assignments.length < 2) return;
69
+
70
+ this.logMISRAError(`Expression ${exprRoot.code} contains a pre/post inc/decrement operator and other side effects.`, new Fix(exprRoot, ($jp: Joinpoint) => {
71
+ const jps = Query.searchFrom($jp, UnaryOp, {kind: /(post_inc)|(post_dec)|(pre_inc)|(pre_dec)/}, TraversalType.POSTORDER).get();
72
+ const calls = Query.searchFromInclusive($jp, Call).get();
73
+ const assignments = Query.searchFromInclusive($jp, BinaryOp, {isAssignment: true}).get();
74
+
75
+ const transformationNo = (calls.length === 0 && assignments.length === 0) ? jps.length - 1 : jps.length;
76
+
77
+ for (let i = 0; i < transformationNo; i++) {
78
+ const jp = jps[i];
79
+ if (/post_.*/.test(jp.kind)) {
80
+ $jp.insertAfter(jp.deepCopy());
81
+ }
82
+ else {
83
+ $jp.insertBefore(jp.deepCopy());
84
+ }
85
+ jp.replaceWith(jp.operand);
86
+ }
87
+ }));
88
+ }
89
+
90
+ private r13_3_noIncrementSideEffects($startNode: Joinpoint) { //not working for decls
91
+ if (!($startNode instanceof ExprStmt)) return;
92
+
93
+ this.checkIncrementSideEffects($startNode.expr);
94
+
95
+ //S13_SideEffectPass.visitAllExprs(this.checkIncrementSideEffects.bind(this), $startNode.expr);
96
+ }
97
+
98
+ private r13_4_noUseOfAssignmentValue($startNode: Joinpoint) {
99
+ if (!($startNode instanceof BinaryOp && $startNode.isAssignment)) return;
100
+
101
+ if (!$startNode.parent.instanceOf("exprStmt") && !($startNode.parent.instanceOf("parenExpr") && $startNode.parent?.parent?.instanceOf("exprStmt"))) {
102
+ this.logMISRAError(`Value of assignment expression ${$startNode.code} should not be used.`);
103
+ }
104
+ }
105
+
106
+ private r13_5_shortCircuitSideEffects($startNode: Joinpoint) {
107
+ if (!($startNode instanceof BinaryOp && /(\&\&|\|\|)/.test($startNode.operator))) return;
108
+
109
+ this.checkPotentialPersistentSideEffects<BinaryOp>($startNode, "RHS of && or || expression", jp => jp.right);
110
+ }
111
+
112
+ private r13_6_sizeofSideEffects($startNode: Joinpoint) {
113
+ if (!($startNode instanceof UnaryExprOrType && $startNode.hasArgExpr)) return;
114
+
115
+ //TODO: exception for volatile qualified lvalue that is not a variable length array
116
+ this.checkPotentialPersistentSideEffects<Joinpoint>($startNode.argExpr, "Sizeof operand", jp => jp);
117
+ }
118
+
119
+ protected _name: string = "Side effects";
120
+
121
+ }