@specs-feup/clava-misra 1.0.1 → 1.0.2

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 (26) hide show
  1. package/package.json +5 -1
  2. package/src/MISRA.ts +12 -2
  3. package/src/MISRAContext.ts +12 -19
  4. package/src/MISRARule.ts +7 -20
  5. package/src/MISRATool.ts +18 -19
  6. package/src/rules/Section16_SwitchStatements/Rule_16_2_TopLevelSwitch.ts +2 -1
  7. package/src/rules/Section16_SwitchStatements/Rule_16_3_UnconditionalBreak.ts +1 -0
  8. package/src/rules/Section16_SwitchStatements/Rule_16_4_SwitchHasDefault.ts +0 -5
  9. package/src/rules/Section16_SwitchStatements/Rule_16_5_DefaultFirstOrLast.ts +1 -1
  10. package/src/rules/Section16_SwitchStatements/Rule_16_6_SwitchMinTwoClauses.ts +7 -3
  11. package/src/rules/Section16_SwitchStatements/Rule_16_7_NonBooleanSwitchCondition.ts +1 -0
  12. package/src/rules/Section17_Functions/Rule_17_3_ImplicitFunction.ts +180 -0
  13. package/src/rules/Section17_Functions/Rule_17_4_NonVoidReturn.ts +34 -21
  14. package/src/rules/Section17_Functions/Rule_17_7_UnusedReturnValue.ts +3 -1
  15. package/src/rules/Section2_UnusedCode/Rule_2_3_UnusedTypeDecl.ts +7 -4
  16. package/src/rules/Section2_UnusedCode/Rule_2_4_UnusedTagDecl.ts +4 -3
  17. package/src/rules/Section2_UnusedCode/Rule_2_6_UnusedLabels.ts +2 -1
  18. package/src/rules/Section2_UnusedCode/Rule_2_7_UnusedParameters.ts +9 -7
  19. package/src/rules/Section3_Comments/Rule_3_1_CommentSequences.ts +1 -1
  20. package/src/rules/index.ts +9 -5
  21. package/src/tests/Section16_SwitchStatements/Rule_16_4_SwitchHasDefault.test.ts +2 -2
  22. package/src/tests/Section17_Functions/Rule_17_3_ImplicitFunctions.test.ts +79 -0
  23. package/src/tests/Section17_Functions/Rule_17_4_NonVoidReturn.test.ts +17 -4
  24. package/src/tests/Section17_Functions/misra_config.json +17 -8
  25. package/src/tests/utils.ts +15 -3
  26. package/src/utils/utils.ts +82 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specs-feup/clava-misra",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "author": "L. Sousa",
5
5
  "description": "Clava library to automatically detect and correct violations of the MISRA-C:2012 standard in C code",
6
6
  "type": "module",
@@ -30,6 +30,10 @@
30
30
  "test:cov": "npm run test -- --coverage",
31
31
  "test:watch": "npm run test -- --watch"
32
32
  },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/specs-feup/clava-misra.git"
36
+ },
33
37
  "dependencies": {
34
38
  "@specs-feup/clava": "^3.0.11",
35
39
  "@specs-feup/clava-visualization": "^1.0.5",
package/src/MISRA.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { BinaryOp, Break, Case, Expression, If, Joinpoint, Scope, Statement, Switch } from "@specs-feup/clava/api/Joinpoints.js";
1
+ import { BinaryOp, Break, Case, Expression, If, Joinpoint, Program, Scope, Statement, Switch } from "@specs-feup/clava/api/Joinpoints.js";
2
2
  import { getNumOfSwitchClauses, isCommentStmt } from "./utils/utils.js";
3
3
  import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
4
+ import Query from "@specs-feup/lara/api/weaver/Query.js";
5
+ import Clava from "@specs-feup/clava/api/clava/Clava.js";
4
6
 
5
7
  export enum MISRATransformationType {
6
8
  NoChange,
@@ -14,7 +16,7 @@ export enum MISRATransformationType {
14
16
  */
15
17
  export class MISRAError {
16
18
  /**
17
- * Represents the specific MISRA rule that was violated
19
+ * Represents the specific MISRA-C rule that was violated
18
20
  */
19
21
  public ruleID: string;
20
22
  /**
@@ -48,6 +50,13 @@ export class MISRAError {
48
50
  this.$jp.astId === other.$jp.astId &&
49
51
  this.message === other.message;
50
52
  }
53
+
54
+ /**
55
+ *
56
+ */
57
+ isActiveError(): boolean {
58
+ return (Query.root() as Joinpoint).contains(this.$jp);
59
+ }
51
60
  }
52
61
 
53
62
  /**
@@ -258,6 +267,7 @@ export class MISRASwitchConverter {
258
267
  private static organizeCaseGroups(caseGroups: Case[][]): Case[][] {
259
268
  const nonDefaultGroups = caseGroups.filter(block => !block.some(caseStmt => caseStmt.isDefault));
260
269
  const defaultBlock = caseGroups.find(block => block.some(caseStmt => caseStmt.isDefault));
270
+
261
271
  if (defaultBlock) {
262
272
  nonDefaultGroups.push(defaultBlock);
263
273
  }
@@ -11,10 +11,6 @@ export default class MISRAContext {
11
11
  * List of MISRA errors, that could not be resolved during the transformation process
12
12
  */
13
13
  #misraErrors: MISRAError[] = [];
14
- /**
15
- * List of MISRA warnings generated by transformations that may alter the program's behavior
16
- */
17
- #misraWarnings: MISRAError[] = [];
18
14
 
19
15
  /**
20
16
  * Configuration provided by the user to assist in rule corrections
@@ -33,8 +29,8 @@ export default class MISRAContext {
33
29
  return this.#misraErrors;
34
30
  }
35
31
 
36
- get warnings(): MISRAError[] {
37
- return this.#misraWarnings;
32
+ get activeErrors(): MISRAError[] {
33
+ return this.#misraErrors.filter(error => error.isActiveError());
38
34
  }
39
35
 
40
36
  get config(): Map<string, any> | undefined {
@@ -71,21 +67,18 @@ export default class MISRAContext {
71
67
  }
72
68
  }
73
69
 
74
- addMISRAWarning(ruleID: string, $jp: Joinpoint, message: string) {
75
- const newWarning = new MISRAError(ruleID, $jp, message);
76
-
77
- if (!this.#misraWarnings.some(warning => warning.equals(newWarning))) {
78
- this.#misraWarnings.push(newWarning);
79
- }
70
+ private printError(error: MISRAError): void {
71
+ console.log(`MISRA-C Rule ${error.ruleID} violation at ${error.$jp.filepath}@${error.$jp.line}:${error.$jp.column}: ${error.message}\n`);
80
72
  }
81
-
82
- printErrors() {
83
- this.#misraErrors.forEach(error => console.log(error.message));
84
- console.log('\n');
73
+
74
+ public printAllErrors(): void {
75
+ this.#misraErrors.forEach(error => this.printError(error));
85
76
  }
86
77
 
87
- printWarnings() {
88
- this.#misraWarnings.forEach(warning => console.log(warning.message));
89
- console.log('\n');
78
+ public printActiveErrors(): void {
79
+ this.#misraErrors
80
+ .filter(error => error.isActiveError())
81
+ .forEach(error => this.printError(error));
90
82
  }
83
+
91
84
  }
package/src/MISRARule.ts CHANGED
@@ -15,6 +15,11 @@ export default abstract class MISRARule {
15
15
  */
16
16
  readonly ruleID: string;
17
17
 
18
+ /**
19
+ * Priority of the rule which is low by default.
20
+ */
21
+ readonly priority: number = 4;
22
+
18
23
  /**
19
24
  * MISRA context for error tracking and rule transformations state
20
25
  */
@@ -54,24 +59,6 @@ export default abstract class MISRARule {
54
59
  * @param msg - Description of the violation
55
60
  */
56
61
  protected logMISRAError($jp: Joinpoint, msg:string): void {
57
- this.context.addMISRAError(
58
- this.ruleID,
59
- $jp,
60
- `MISRA-C Rule ${this.ruleID} violation at ${$jp.filepath}@${$jp.line}:${$jp.column}: ${msg}`
61
- )
62
- }
63
-
64
- /**
65
- * Logs a warning from automatic MISRA-C correction, which may change the program's behavior
66
- *
67
- * @param $jp - The joinpoint where the correction was applied
68
- * @param msg - Description of the warning
69
- */
70
- protected logMISRAWarning($jp: Joinpoint, msg:string): void {
71
- this.context.addMISRAWarning(
72
- this.ruleID,
73
- $jp,
74
- `Warning: MISRA-C Rule ${this.ruleID} correction at ${$jp.filepath}@${$jp.line}:${$jp.column}: ${msg}.`
75
- )
62
+ this.context.addMISRAError(this.ruleID, $jp, msg);
76
63
  }
77
- }
64
+ }
package/src/MISRATool.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import Query from "@specs-feup/lara/api/weaver/Query.js";
2
- import { FileJp, Joinpoint, Program } from "@specs-feup/clava/api/Joinpoints.js";
2
+ import { Call, FileJp, Joinpoint, Program } from "@specs-feup/clava/api/Joinpoints.js";
3
3
  import MISRARule from "./MISRARule.js";
4
- import misraRules from "./rules/index.js";
4
+ import sortRules from "./rules/index.js";
5
5
  import MISRAContext from "./MISRAContext.js";
6
6
  import { MISRAError, MISRATransformationType } from "./MISRA.js";
7
+ import { isCallToImplicitFunction } from "./utils/utils.js";
7
8
 
8
9
  export default class MISRATool {
9
10
  static #misraRules: MISRARule[];
@@ -12,7 +13,7 @@ export default class MISRATool {
12
13
  private static init(startingPoint: FileJp | Program) {
13
14
  this.validateStdVersion(startingPoint);
14
15
  this.#context = new MISRAContext();
15
- this.#misraRules = misraRules(this.#context);
16
+ this.#misraRules = sortRules(this.#context);
16
17
  }
17
18
 
18
19
  private static validateStdVersion(startingPoint: FileJp | Program) {
@@ -28,21 +29,21 @@ export default class MISRATool {
28
29
  public static checkCompliance(startingPoint: Program | FileJp = Query.root() as Program) {
29
30
  this.init(startingPoint);
30
31
 
31
- const nodes = startingPoint.descendants;
32
+ const nodes = [startingPoint, ...startingPoint.descendants];
32
33
  for (const node of nodes) {
33
34
  for (const rule of this.#misraRules) {
34
35
  rule.match(node, true);
35
36
  }
36
37
  }
37
38
  if (this.#context.errors.length > 0) {
38
- this.#context.printErrors();
39
+ this.#context.printAllErrors();
39
40
  } else {
40
41
  console.log("[Clava-MISRATool] No MISRA-C violations detected.");
41
42
  }
42
43
  }
43
44
 
44
- public static applyCorrections(configFilePath?: string, startingPoint: Program | FileJp = Query.root() as Program) {
45
- this.init(startingPoint);
45
+ public static applyCorrections(configFilePath?: string) {
46
+ this.init(Query.root() as Program);
46
47
  if (configFilePath) {
47
48
  this.#context.config = configFilePath;
48
49
  }
@@ -51,20 +52,14 @@ export default class MISRATool {
51
52
  let modified = false;
52
53
  do {
53
54
  console.log(`[Clava-MISRATool] Iteration #${++iteration}: Applying MISRA-C transformations...`);
54
- modified = this.transformAST(startingPoint);
55
+ modified = this.transformAST(Query.root() as Program);
55
56
  } while(modified);
56
57
 
57
- if (this.#context.errors.length === 0 && this.#context.warnings.length === 0) {
58
+ if (this.#context.errors.length === 0) {
58
59
  console.log("[Clava-MISRATool] All detected violations were corrected.");
59
60
  } else {
60
- if (this.#context.warnings.length > 0) {
61
- console.log("\n[Clava-MISRATool] Warnings from automatic MISRA-C corrections (these may change the program's behavior):");
62
- this.#context.printWarnings();
63
- }
64
- if (this.#context.errors.length > 0) {
65
- console.log("\n[Clava-MISRATool] Remaining MISRA-C violations:");
66
- this.#context.printErrors();
67
- }
61
+ console.log("\n[Clava-MISRATool] Remaining MISRA-C violations:");
62
+ this.#context.printActiveErrors();
68
63
  }
69
64
  }
70
65
 
@@ -90,7 +85,11 @@ export default class MISRATool {
90
85
  return modified;
91
86
  }
92
87
 
93
- public static getMISRAErrors(): MISRAError[] {
94
- return this.#context.errors;
88
+ public static getErrorCount(): number {
89
+ return this.#context.errors.length;
90
+ }
91
+
92
+ public static getActiveErrorCount(): number {
93
+ return this.#context.activeErrors.length;
95
94
  }
96
95
  }
@@ -5,10 +5,11 @@ import Query from "@specs-feup/lara/api/weaver/Query.js";
5
5
  import { MISRATransformationReport, MISRATransformationType } from "../../MISRA.js";
6
6
 
7
7
  /**
8
- * MISRA Rule 16.2: A switch label shall only be used when the most closely-enclos ing
8
+ * MISRA Rule 16.2: A switch label shall only be used when the most closely-enclosing
9
9
  compound statement is the body of a switch statement
10
10
  */
11
11
  export default class Rule_16_2_TopLevelSwitch extends MISRARule {
12
+ priority = 2;
12
13
  #misplacedCases: Case[] = [];
13
14
 
14
15
  constructor(context: MISRAContext) {
@@ -9,6 +9,7 @@ import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
9
9
  * MISRA Rule 16.3: An unconditional break statement shall terminate every switch-clause
10
10
  */
11
11
  export default class Rule_16_3_UnconditionalBreak extends MISRARule {
12
+ priority = 2;
12
13
  #statementsNeedingBreakAfter: Joinpoint[] = [];
13
14
 
14
15
  constructor(context: MISRAContext) {
@@ -32,11 +32,6 @@ export default class Rule_16_4_SwitchHasDefault extends MISRARule {
32
32
  transform($jp: Joinpoint): MISRATransformationReport {
33
33
  if (!this.match($jp)) return new MISRATransformationReport(MISRATransformationType.NoChange);
34
34
 
35
- if ((getNumOfSwitchClauses($jp as Switch) < 2 || switchHasBooleanCondition($jp as Switch)) &&
36
- !switchHasConditionalBreak($jp as Switch)) { // Will be handled by rules 16.6 or 16.7
37
- return new MISRATransformationReport(MISRATransformationType.NoChange);
38
- }
39
-
40
35
  $jp.children[1].lastChild
41
36
  .insertAfter(ClavaJoinPoints.defaultStmt())
42
37
  .insertAfter(ClavaJoinPoints.emptyStmt())
@@ -88,7 +88,7 @@ export default class Rule_16_5_DefaultFirstOrLast extends MISRARule {
88
88
  const defaultCase = ($jp as Switch).getDefaultCase;
89
89
  const rightStatements = defaultCase.siblingsRight.filter(sibling => !isCommentStmt(sibling));
90
90
 
91
- // Reposition the default case to the last position within its case clause lis
91
+ // Reposition the default case to the last position within its case clause list
92
92
  if (rightStatements[0] instanceof Case) { // At least one of the first statements is a Case
93
93
  const rightConsecutiveCases = this.getConsecutiveRightCases(defaultCase);
94
94
  const lastRightCase = rightConsecutiveCases[rightConsecutiveCases.length - 1];
@@ -8,6 +8,7 @@ import { getNumOfSwitchClauses, switchHasConditionalBreak } from "../../utils/ut
8
8
  * MISRA Rule 16.6: Every switch statement shall have at least two switch-clauses.
9
9
  */
10
10
  export default class Rule_16_6_SwitchMinTwoClauses extends MISRARule {
11
+ priority = 3;
11
12
 
12
13
  constructor(context: MISRAContext) {
13
14
  super("16.6", context);
@@ -39,12 +40,15 @@ export default class Rule_16_6_SwitchMinTwoClauses extends MISRARule {
39
40
  transform($jp: Joinpoint): MISRATransformationReport {
40
41
  if (!this.match($jp)) return new MISRATransformationReport(MISRATransformationType.NoChange);
41
42
 
42
- if (switchHasConditionalBreak($jp as Switch)) {
43
- this.logMISRAError($jp, "switch statement must have at least two clauses and cannot be transformed due to a conditional break statement.")
43
+ const switchJp = $jp as Switch;
44
+ if (switchHasConditionalBreak(switchJp)) {
45
+ if (switchJp.hasDefaultCase) {
46
+ this.logMISRAError($jp, "Switch statement must have at least two clauses and cannot be transformed due to a conditional break statement.")
47
+ }
44
48
  return new MISRATransformationReport(MISRATransformationType.NoChange);
45
49
  }
46
50
 
47
- const transformResultNode = MISRASwitchConverter.convert($jp as Switch);
51
+ const transformResultNode = MISRASwitchConverter.convert(switchJp);
48
52
  if (transformResultNode) {
49
53
  return new MISRATransformationReport(
50
54
  MISRATransformationType.Replacement,
@@ -8,6 +8,7 @@ import { switchHasBooleanCondition, switchHasConditionalBreak } from "../../util
8
8
  * MISRA Rule 16.7: A switch-expression shall not have essentially Boolean type.
9
9
  */
10
10
  export default class Rule_16_7_NonBooleanSwitchCondition extends MISRARule {
11
+ priority = 3;
11
12
  constructor(context: MISRAContext) {
12
13
  super("16.7", context);
13
14
  }
@@ -0,0 +1,180 @@
1
+ import { Call, FileJp, Joinpoint, Program } from "@specs-feup/clava/api/Joinpoints.js";
2
+ import MISRARule from "../../MISRARule.js";
3
+ import MISRAContext from "../../MISRAContext.js";
4
+ import { MISRATransformationReport, MISRATransformationType } from "../../MISRA.js";
5
+ import { getFilesWithCallToImplicitFunction, getIncludesOfFile, isCallToImplicitFunction, isValidFile, removeIncludeFromFile } from "../../utils/utils.js";
6
+ import Query from "@specs-feup/lara/api/weaver/Query.js";
7
+ import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
8
+
9
+ /**
10
+ * MISRA Rule 17.3: A function shall not be declared implicitly
11
+ */
12
+ export default class Rule_17_3_ImplicitFunction extends MISRARule {
13
+ priority = 1;
14
+
15
+ constructor(context: MISRAContext) {
16
+ super("17.3", context);
17
+ }
18
+
19
+ /**
20
+ * Checks if the given joinpoint represents a call to an implicit function.
21
+ *
22
+ * @param $jp - Joinpoint to analyze
23
+ * @param logErrors - [logErrors=false] - Whether to log errors if a violation is detected
24
+ * @returns Returns true if the joinpoint violates the rule, false otherwise
25
+ */
26
+ match($jp: Joinpoint, logErrors: boolean = false): boolean {
27
+ if (!($jp instanceof Program)) return false;
28
+
29
+ const implicitCalls = Query.searchFrom($jp, Call, (callJp) => isCallToImplicitFunction(callJp)). get();
30
+ for (const callJp of implicitCalls) {
31
+ if (logErrors) {
32
+ this.logMISRAError(callJp, `Function '${callJp.name}' is declared implicitly.`);
33
+ }
34
+ }
35
+ return implicitCalls.length > 0;
36
+ }
37
+
38
+ /**
39
+ * Transforms every implicit call by adding a missing include directive or extern statement specified on the config file.
40
+ *
41
+ * - If the configuration is missing or the specified fix is invalid (i.e., not a '.h' or '.c'), no transformation is performed and the call is left unchanged.
42
+ * - The fix is applied only if it successfully resolves the issue (i.e., makes the call explicit and the file compiles with no error).
43
+ * - Otherwise, the fix is removed.
44
+ *
45
+ * @param $jp - Joinpoint to transform
46
+ * @returns Report detailing the transformation result
47
+ */
48
+ transform($jp: Joinpoint): MISRATransformationReport {
49
+ if (!this.match($jp)) return new MISRATransformationReport(MISRATransformationType.NoChange);
50
+
51
+ const programJp = $jp as Program;
52
+ const filesWithImplicitCall = getFilesWithCallToImplicitFunction(programJp);
53
+ let changedDescendant = false;
54
+
55
+ for (const fileJp of filesWithImplicitCall) {
56
+ changedDescendant = changedDescendant || this.solveImplicitCalls(fileJp);
57
+ }
58
+
59
+ if (changedDescendant) {
60
+ programJp.rebuild();
61
+ return new MISRATransformationReport(MISRATransformationType.Replacement, Query.root() as Program);
62
+ } else {
63
+ return new MISRATransformationReport(MISRATransformationType.NoChange);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Retrieves the fix for a implicit call specified on the config file (.h or .c)
69
+ * @param callJp
70
+ * @param errorMsgPrefix
71
+ * @returns
72
+ */
73
+ private getImplicitFixFromConfig(callJp: Call, errorMsgPrefix: string): string | undefined {
74
+ if (!this.context.config) {
75
+ this.logMISRAError(callJp, `${errorMsgPrefix} Include or extern not added due to missing config file.`);
76
+ return undefined;
77
+ }
78
+
79
+ let configFix: string | undefined;
80
+ try {
81
+ configFix = this.context.config.get("implicitCalls")[callJp.name];
82
+ } catch {
83
+ this.logMISRAError(callJp, `${errorMsgPrefix} Include or extern was not added as \'implicitCalls\' is not defined in the configuration file.`);
84
+ return undefined;
85
+ }
86
+
87
+ if (configFix === undefined) {
88
+ this.logMISRAError(callJp, `${errorMsgPrefix} Couldn't add include or extern due to missing configuration for function '${callJp.name}'.`);
89
+ return undefined;
90
+ }
91
+
92
+ if (!(configFix.endsWith(".h") || configFix.endsWith(".c"))) {
93
+ this.logMISRAError(callJp, `${errorMsgPrefix} Cannot add include or extern without a .h or .c reference.`);
94
+ return undefined;
95
+ }
96
+ return configFix;
97
+ }
98
+
99
+ /**
100
+ * Attempts to resolve implicit function calls in a file by adding missing includes or extern statements based on the configuration file.
101
+ * @param fileJp The file to analyze
102
+ * @returns `true` if any changes were made to the file, otherwise `false`.
103
+ */
104
+ private solveImplicitCalls(fileJp: FileJp): boolean {
105
+ const implicitCalls = Query.searchFrom(fileJp, Call, (callJp) => (isCallToImplicitFunction(callJp))).get();
106
+ const originalIncludes = getIncludesOfFile(fileJp);
107
+ let solvedCalls = new Set<string>();
108
+ let addedIncludes: string[] = [];
109
+ let changedFile = false;
110
+
111
+ for (const callJp of implicitCalls) {
112
+ const errorMsgPrefix = `Function '${callJp.name}' is declared implicitly.`;
113
+
114
+ if (solvedCalls.has(callJp.name)) {
115
+ continue;
116
+ }
117
+
118
+ const configFix = this.getImplicitFixFromConfig(callJp, errorMsgPrefix);
119
+ if (!configFix) {
120
+ continue;
121
+ }
122
+
123
+ const callIndex = Query.searchFrom(fileJp, Call, { name: callJp.name }).get().findIndex(c => c.equals(callJp));
124
+ const isInclude = configFix.endsWith(".h");
125
+ if (isInclude) {
126
+ if (originalIncludes.includes(configFix)) {
127
+ this.logMISRAError(callJp, `${errorMsgPrefix} Provided include \'${configFix}\' does not fix the violation.`);
128
+ }
129
+ else if (addedIncludes.includes(configFix)) {
130
+
131
+ if (this.isValidFileWithExplicitCall(fileJp, callJp.name, callIndex)) {
132
+ solvedCalls.add(callJp.name);
133
+ } else {
134
+ this.logMISRAError(callJp, `${errorMsgPrefix} Provided include \'${configFix}\' does not fix the violation.`);
135
+ }
136
+ }
137
+ else {
138
+ fileJp.addInclude(configFix);
139
+ const fileCompiles = this.isValidFileWithExplicitCall(fileJp, callJp.name, callIndex);
140
+
141
+ if (fileCompiles) {
142
+ solvedCalls.add(callJp.name);
143
+ addedIncludes.push(configFix);
144
+ changedFile = true;
145
+ } else {
146
+ removeIncludeFromFile(configFix, fileJp);
147
+ this.logMISRAError(callJp, `${errorMsgPrefix} Provided include \'${configFix}\' does not fix the violation.`);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return changedFile;
153
+ }
154
+
155
+ /**
156
+ * Checks if the rebuilt version of the file compiles and if the provided call is no longer implicit
157
+ * @param fileJp The file to analyze
158
+ * @param funcName The function name to search the call
159
+ * @param callIndex The index of the call
160
+ */
161
+ private isValidFileWithExplicitCall(fileJp: FileJp, funcName: string, callIndex: number) {
162
+ const programJp = fileJp.parent as Program;
163
+ let copyFile = ClavaJoinPoints.fileWithSource(`temp_misra_${fileJp.name}`, fileJp.code, fileJp.relativeFolderpath);
164
+
165
+ copyFile = programJp.addFile(copyFile) as FileJp;
166
+ try {
167
+ const rebuiltFile = copyFile.rebuild();
168
+ const fileToRemove = Query.searchFrom(programJp, FileJp, {filepath: rebuiltFile.filepath}).first() as FileJp;
169
+ const callJp = Query.searchFrom(fileToRemove, Call, {name: funcName}).get().at(callIndex);
170
+ const isExplicitCall = callJp !== undefined && !isCallToImplicitFunction(callJp);
171
+
172
+ fileToRemove?.detach();
173
+ return isExplicitCall;
174
+
175
+ } catch(error) {
176
+ copyFile.detach();
177
+ return false;
178
+ }
179
+ }
180
+ }
@@ -1,14 +1,15 @@
1
- import { BuiltinType, Call, EnumDecl, ExprStmt, FunctionJp, Joinpoint, ReturnStmt } from "@specs-feup/clava/api/Joinpoints.js";
1
+ import { BuiltinType, FileJp, FunctionJp, Joinpoint, ReturnStmt } from "@specs-feup/clava/api/Joinpoints.js";
2
2
  import MISRARule from "../../MISRARule.js";
3
3
  import MISRAContext from "../../MISRAContext.js";
4
4
  import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
5
5
  import { MISRATransformationReport, MISRATransformationType } from "../../MISRA.js";
6
6
  import Query from "@specs-feup/lara/api/weaver/Query.js";
7
+ import { isValidFile } from "../../utils/utils.js";
7
8
 
8
9
  /**
9
10
  * MISRA Rule 17.4: All exit paths from a function with non-void return type shall have an
10
11
  explicit return statement with an expression. In a non-void function:
11
- - Every return statement has an expression, and a
12
+ - Every return statement has an expression, and
12
13
  - Control cannot reach the end of the function without encountering a return statement
13
14
  */
14
15
  export default class Rule_17_4_NonVoidReturn extends MISRARule {
@@ -43,7 +44,7 @@ export default class Rule_17_4_NonVoidReturn extends MISRARule {
43
44
 
44
45
  /**
45
46
  * Transforms a non-void function joinpoint that has no return statement at the end, by adding a default return value based on the config file.
46
- * - If the configuration file is is missing or invalid, no transformation is performed.
47
+ * - If the configuration file is missing/invalid or the specified default value is invalid, no transformation is performed and the function is left unchanged.
47
48
  * - Otherwise, a return statement is inserted as the last statement of the function.
48
49
  *
49
50
  * @param $jp - Joinpoint to transform
@@ -53,26 +54,38 @@ export default class Rule_17_4_NonVoidReturn extends MISRARule {
53
54
  if (!this.match($jp)) return new MISRATransformationReport(MISRATransformationType.NoChange);
54
55
 
55
56
  const functionJp = $jp as FunctionJp;
56
- if (this.context.config) {
57
- try {
58
- const returnType = functionJp.type.code;
59
- const defaultValueReturn = this.context.config.get("defaultValues")[returnType];
57
+ const fileJp = functionJp.getAncestor("file") as FileJp;
58
+ const returnType = functionJp.type.code;
59
+ const errorMsgPrefix = `Function '${functionJp.name}' reaches the end without a return statement.`;
60
60
 
61
- if (defaultValueReturn === undefined) {
62
- this.logMISRAError($jp, `Function '${functionJp.name}' reaches the end without a return statement. Default value return not added due to missing default value configuration for type '${returnType}'.`);
63
- return new MISRATransformationReport(MISRATransformationType.NoChange);
64
- }
65
- const newJp = ClavaJoinPoints.returnStmt(ClavaJoinPoints.exprLiteral(String(defaultValueReturn), functionJp.returnType)) as ReturnStmt;
66
- functionJp.body.lastChild ? functionJp.body.lastChild.insertAfter(newJp) : functionJp.body.setFirstChild(newJp);
61
+ if (!this.context.config) {
62
+ this.logMISRAError($jp, `${errorMsgPrefix} Default value return not added due to missing config file.`)
63
+ return new MISRATransformationReport(MISRATransformationType.NoChange);
64
+ }
67
65
 
68
- return new MISRATransformationReport(MISRATransformationType.DescendantChange);
69
-
70
- } catch (error) {
71
- this.logMISRAError($jp, `Function '${functionJp.name}' reaches the end without a return statement. Default value return not added due to invalid structure of configuration file.`);
72
- return new MISRATransformationReport(MISRATransformationType.NoChange);
73
- }
74
- }
75
- this.logMISRAError($jp, `Function '${functionJp.name}' reaches the end without a return statement. Default value return not added due to missing config file.`)
66
+ let defaultValueReturn;
67
+ try {
68
+ defaultValueReturn = this.context.config.get("defaultValues")[returnType];
69
+ } catch (error) {
70
+ this.logMISRAError($jp, `${errorMsgPrefix} Default value return was not added as \'defaultValues\' is not defined in the configuration file.`);
71
+ return new MISRATransformationReport(MISRATransformationType.NoChange);
72
+ }
73
+
74
+ if (defaultValueReturn === undefined) {
75
+ this.logMISRAError($jp, `${errorMsgPrefix} Default value return not added due to missing default value configuration for type '${returnType}'.`);
76
+ return new MISRATransformationReport(MISRATransformationType.NoChange);
77
+ }
78
+
79
+ // Insert return statement and validate it
80
+ const returnStmt = ClavaJoinPoints.returnStmt(ClavaJoinPoints.exprLiteral(String(defaultValueReturn), functionJp.returnType)) as ReturnStmt;
81
+ functionJp.body.lastChild ? functionJp.body.lastChild.insertAfter(returnStmt) : functionJp.body.setFirstChild(returnStmt);
82
+
83
+ // Validate the provided default value. If it is invalid, the return stmt is removed
84
+ if (isValidFile(fileJp)) {
85
+ return new MISRATransformationReport(MISRATransformationType.DescendantChange);
86
+ }
87
+ returnStmt.detach();
88
+ this.logMISRAError($jp, `${errorMsgPrefix} Provided default value for type '${functionJp.type.code}' is invalid and was therefore not inserted.`);
76
89
  return new MISRATransformationReport(MISRATransformationType.NoChange);
77
90
  }
78
91
  }
@@ -3,6 +3,7 @@ import MISRARule from "../../MISRARule.js";
3
3
  import MISRAContext from "../../MISRAContext.js";
4
4
  import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
5
5
  import { MISRATransformationReport, MISRATransformationType } from "../../MISRA.js";
6
+ import { isCallToImplicitFunction } from "../../utils/utils.js";
6
7
 
7
8
  /**
8
9
  * MISRA Rule 17.7: The value returned by a function having non-void return type shall be
@@ -41,7 +42,8 @@ export default class Rule_17_7_UnusedReturnValue extends MISRARule {
41
42
  transform($jp: Joinpoint): MISRATransformationReport {
42
43
  if (!this.match($jp)) return new MISRATransformationReport(MISRATransformationType.NoChange);
43
44
 
44
- const newJp = ClavaJoinPoints.cStyleCast(ClavaJoinPoints.type("void"), $jp as Call);
45
+ const callJp = $jp as Call;
46
+ const newJp = ClavaJoinPoints.cStyleCast(ClavaJoinPoints.type("void"), callJp);
45
47
  return new MISRATransformationReport(MISRATransformationType.Replacement, $jp.replaceWith(newJp));
46
48
  }
47
49
  }
@@ -8,6 +8,7 @@ import { getBaseType, getTagUses, getTypeDecl, getTypedJps } from "../../utils/u
8
8
  * MISRA-C Rule 2.3: A project should not contain unused type declarations.
9
9
  */
10
10
  export default class Rule_2_3_UnusedTypeDecl extends MISRARule {
11
+ priority = 3;
11
12
 
12
13
  constructor(context: MISRAContext) {
13
14
  super("2.3", context);
@@ -65,11 +66,13 @@ export default class Rule_2_3_UnusedTypeDecl extends MISRARule {
65
66
  if (!this.match($jp))
66
67
  return new MISRATransformationReport(MISRATransformationType.NoChange);
67
68
 
68
- if (($jp instanceof RecordJp || $jp instanceof EnumDecl) && $jp.name && getTagUses($jp)) {
69
+ if (($jp instanceof RecordJp || $jp instanceof EnumDecl) && $jp.name && getTagUses($jp).length > 0) {
69
70
  $jp.lastChild.detach();
70
71
  return new MISRATransformationReport(MISRATransformationType.DescendantChange);
71
- }
72
- $jp.detach();
73
- return new MISRATransformationReport(MISRATransformationType.Removal);
72
+ } else {
73
+ $jp.detach();
74
+ return new MISRATransformationReport(MISRATransformationType.Removal);
75
+ }
76
+
74
77
  }
75
78
  }
@@ -8,7 +8,8 @@ import { getTagUses, hasTypeDecl } from "../../utils/utils.js";
8
8
  * MISRA-C Rule 2.4: A project should not contain unused tag declarations.
9
9
  */
10
10
  export default class Rule_2_4_UnusedTagDecl extends MISRARule {
11
-
11
+ priority = 3;
12
+
12
13
  constructor(context: MISRAContext) {
13
14
  super("2.4", context);
14
15
  }
@@ -40,8 +41,8 @@ export default class Rule_2_4_UnusedTagDecl extends MISRARule {
40
41
 
41
42
  /**
42
43
  * Transforms the joinpoint if it is an unused tag declaration
43
- * - If the Joinpoint is a tag declared in a typedef, it removes the name.
44
- * - Otherwise, the Joinpoint is detached.
44
+ * - If the joinpoint is a tag declared in a typedef, it removes the name.
45
+ * - Otherwise, the joinpoint is detached.
45
46
  *
46
47
  * @param $jp - Joinpoint to transform
47
48
  * @returns Report detailing the transformation result
@@ -9,7 +9,8 @@ import { MISRATransformationReport, MISRATransformationType } from "../../MISRA.
9
9
  * Checks for labels within a function that are not used.
10
10
  */
11
11
  export default class Rule_2_6_UnusedLabels extends MISRARule {
12
-
12
+ priority = 3;
13
+
13
14
  constructor(context: MISRAContext) {
14
15
  super("2.6", context);
15
16
  }
@@ -4,8 +4,10 @@ import MISRAContext from "../../MISRAContext.js";
4
4
  import Query from "@specs-feup/lara/api/weaver/Query.js";
5
5
  import { MISRATransformationReport, MISRATransformationType } from "../../MISRA.js";
6
6
  import { getParamReferences } from "../../utils/utils.js";
7
+ import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
7
8
 
8
9
  export default class Rule_2_7_UnusedParameters extends MISRARule {
10
+ priority = 3;
9
11
 
10
12
  constructor(context: MISRAContext) {
11
13
  super("2.7", context);
@@ -43,21 +45,21 @@ export default class Rule_2_7_UnusedParameters extends MISRARule {
43
45
  }
44
46
 
45
47
  transform($jp: Joinpoint): MISRATransformationReport {
46
- if(!this.match($jp))
47
- return new MISRATransformationReport(MISRATransformationType.NoChange);
48
+ if(!this.match($jp)) return new MISRATransformationReport(MISRATransformationType.NoChange);
48
49
 
49
- const usedParams = this.getUsedParams($jp as FunctionJp);
50
- const usedParamsPositions = this.getUsedParamsPositions($jp as FunctionJp);
50
+ const functionJp = $jp as FunctionJp;
51
+ const usedParams = this.getUsedParams(functionJp);
52
+ const usedParamsPositions = this.getUsedParamsPositions(functionJp);
51
53
  const calls = Query.search(Call, {function: jp => jp.astId === $jp.astId}).get();
52
54
 
53
- ($jp as FunctionJp).setParams(usedParams);
54
- for (const funcDecl of ($jp as FunctionJp).declarationJps) {
55
+ functionJp.setParams(usedParams);
56
+ for (const funcDecl of functionJp.declarationJps) {
55
57
  funcDecl.setParams(usedParams);
56
58
  }
57
59
 
58
60
  for (const call of calls) {
59
61
  const newArgs = usedParamsPositions.map(i => call.args[i]);
60
- const newCall = ($jp as FunctionJp).newCall(newArgs)
62
+ const newCall = functionJp.newCall(newArgs)
61
63
  call.replaceWith(newCall);
62
64
  }
63
65
  return new MISRATransformationReport(MISRATransformationType.DescendantChange);
@@ -20,7 +20,7 @@ export default class Rule_3_1_CommentSequences extends MISRARule {
20
20
 
21
21
  if (logErrors) {
22
22
  invalidComments.forEach(comment =>
23
- this.logMISRAError(comment, `Comment ${comment.text} contains invalid character sequences.`)
23
+ this.logMISRAError(comment, `Comment \'${comment.text}\' contains invalid character sequences.`)
24
24
  )
25
25
  }
26
26
  return invalidComments.length > 0;
@@ -1,14 +1,15 @@
1
1
  import MISRAContext from "../MISRAContext.js";
2
+ import MISRARule from "../MISRARule.js";
2
3
  import Rule_16_2_TopLevelSwitch from "./Section16_SwitchStatements/Rule_16_2_TopLevelSwitch.js";
3
4
  import Rule_16_3_UnconditionalBreak from "./Section16_SwitchStatements/Rule_16_3_UnconditionalBreak.js";
4
5
  import Rule_16_4_SwitchHasDefault from "./Section16_SwitchStatements/Rule_16_4_SwitchHasDefault.js";
5
6
  import Rule_16_5_DefaultFirstOrLast from "./Section16_SwitchStatements/Rule_16_5_DefaultFirstOrLast.js";
6
7
  import Rule_16_6_SwitchMinTwoClauses from "./Section16_SwitchStatements/Rule_16_6_SwitchMinTwoClauses.js";
7
8
  import Rule_16_7_NonBooleanSwitchCondition from "./Section16_SwitchStatements/Rule_16_7_NonBooleanSwitchCondition.js";
9
+ import Rule_17_3_ImplicitFunction from "./Section17_Functions/Rule_17_3_ImplicitFunction.js";
8
10
  import Rule_17_4_NonVoidReturn from "./Section17_Functions/Rule_17_4_NonVoidReturn.js";
9
11
  import Rule_17_6_StaticArraySizeParam from "./Section17_Functions/Rule_17_6_StaticArraySizeParam.js";
10
12
  import Rule_17_7_UnusedReturnValue from "./Section17_Functions/Rule_17_7_UnusedReturnValue.js";
11
- import Rule_20_2_InvalidHeaderFileName from "./Section20-PreprocessingDirectives/Rule_20_2_InvalidHeaderFileName.js";
12
13
  import Rule_2_3_UnusedTypeDecl from "./Section2_UnusedCode/Rule_2_3_UnusedTypeDecl.js";
13
14
  import Rule_2_4_UnusedTagDecl from "./Section2_UnusedCode/Rule_2_4_UnusedTagDecl.js";
14
15
  import Rule_2_6_UnusedLabels from "./Section2_UnusedCode/Rule_2_6_UnusedLabels.js";
@@ -16,8 +17,9 @@ import Rule_2_7_UnusedParameters from "./Section2_UnusedCode/Rule_2_7_UnusedPara
16
17
  import Rule_3_1_CommentSequences from "./Section3_Comments/Rule_3_1_CommentSequences.js";
17
18
  import Rule_3_2_CommentSequences from "./Section3_Comments/Rule_3_2_LineSplicing.js";
18
19
 
19
- export function misraRules(context: MISRAContext) {
20
- return [
20
+ export function sortRules(context: MISRAContext) {
21
+
22
+ const rules: MISRARule[] = [
21
23
  new Rule_2_3_UnusedTypeDecl(context),
22
24
  new Rule_2_4_UnusedTagDecl(context),
23
25
  new Rule_2_6_UnusedLabels(context),
@@ -30,11 +32,13 @@ export function misraRules(context: MISRAContext) {
30
32
  new Rule_16_5_DefaultFirstOrLast(context),
31
33
  new Rule_16_6_SwitchMinTwoClauses(context),
32
34
  new Rule_16_7_NonBooleanSwitchCondition(context),
35
+ new Rule_17_3_ImplicitFunction(context),
33
36
  new Rule_17_4_NonVoidReturn(context),
34
37
  new Rule_17_6_StaticArraySizeParam(context),
35
38
  new Rule_17_7_UnusedReturnValue(context),
36
- new Rule_20_2_InvalidHeaderFileName(context),
37
39
  ];
40
+
41
+ return rules.sort((ruleA, ruleB) => ruleA.priority - ruleB.priority);
38
42
  }
39
43
 
40
- export default misraRules;
44
+ export default sortRules;
@@ -61,7 +61,7 @@ const failingCode2 =
61
61
  break;
62
62
  }
63
63
 
64
- switch ( x == 4)
64
+ switch ( x == 4)
65
65
  {
66
66
  case 0:
67
67
  ++x;
@@ -80,7 +80,7 @@ const failingCode3 =
80
80
  `void foo16_4_5( void )
81
81
  {
82
82
  int x, a = 14;
83
- switch (x) { /* Default will not be introduced, as it Will be converted by the other rule*/
83
+ switch (x) { /* Default will not be introduced, as it will be converted by the other rule*/
84
84
  case 1:
85
85
  ++x;
86
86
  break;
@@ -0,0 +1,79 @@
1
+ import { countErrorsAfterCorrection, countMISRAErrors, registerSourceCode, TestFile } from "../utils.js";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ const passingCode = `
6
+ #include <stdio.h>
7
+ int func() {
8
+ return 0;
9
+ }
10
+
11
+ void test_17_3_1() {
12
+ printf("Result: %.2f ", func());
13
+ }`;
14
+
15
+ const failingCode = `
16
+ void test_17_3_2() {
17
+ double a = 2.0, b = 3.0;
18
+
19
+ // Implicit call to pow(): math.h is missing
20
+ double res1 = pow(a, b);
21
+ double res2 = pow(b, a);
22
+
23
+ // Implicit call to printf(): studio.h is missing
24
+ // Return value is not being used
25
+ printf("Results: %.2f %.2f ", res1, res2);
26
+
27
+ // Implicit call to sin(): math.h is missing
28
+ double angle = 3.14159265;
29
+ double sin_val = sin(angle);
30
+
31
+ // Implicit call to printf(): studio.h is missing
32
+ // Return value is not being used
33
+ printf("Sin: %.2f ", sin_val);
34
+
35
+ // Implicit call to print(): studio.h will not solve this issue
36
+ // Return value is not being used
37
+ (void) print("End of program");
38
+
39
+ }`;
40
+
41
+ const failingCode2 = `
42
+ #include <math.h>
43
+
44
+ int func() {
45
+ double a = 2.0, b = 3.0;
46
+
47
+ double pow_result = pow(a, b);
48
+ double sum = half(b) + pow_result; // Implicit call: provided math.h does not solve
49
+
50
+ // Implicit call: provided stdio.h does not solve the issue
51
+ // Return value is not being used
52
+ println("Result: ", sum);
53
+
54
+ return 0;
55
+ }`;
56
+
57
+ const files: TestFile[] = [
58
+ { name: "bad1.c", code: failingCode },
59
+ { name: "bad2.c", code: failingCode2 },
60
+ { name: "good.c", code: passingCode },
61
+ ];
62
+
63
+ describe("Rule 17.4", () => {
64
+ registerSourceCode(files);
65
+
66
+ it("should detect errors", () => {
67
+ expect(countMISRAErrors()).toBe(12);
68
+ });
69
+
70
+ it("should correct errors", () => {
71
+ const __filename = fileURLToPath(import.meta.url);
72
+ const __dirname = path.dirname(__filename);
73
+
74
+ const configFilename = "misra_config.json";
75
+ const configFilePath = path.join(__dirname, configFilename);
76
+
77
+ expect(countErrorsAfterCorrection(configFilePath)).toBe(3);
78
+ });
79
+ });
@@ -30,6 +30,11 @@ const failingCode3 = `
30
30
  GREEN,
31
31
  } Color;
32
32
 
33
+ typedef enum {
34
+ SMALL,
35
+ LARGE
36
+ } Size;
37
+
33
38
  typedef unsigned int my_int_type;
34
39
 
35
40
  // Non-compliant
@@ -47,11 +52,19 @@ const failingCode3 = `
47
52
 
48
53
  }
49
54
 
55
+ /*
56
+ Non-compliant after correction:
57
+ Config file specifies an invalid default value for 'Size' type (e.g: MEDIUM)
58
+ */
59
+ Size test_17_4_6() {
60
+
61
+ }
62
+
50
63
  /*
51
64
  Non-compliant after correction:
52
65
  Config file do not specify the default value for 'double' type
53
66
  */
54
- double test_17_4_6() {
67
+ double test_17_4_7() {
55
68
  }
56
69
  `;
57
70
 
@@ -66,10 +79,10 @@ describe("Rule 17.4", () => {
66
79
  registerSourceCode(files);
67
80
 
68
81
  it("should detect errors", () => {
69
- expect(countMISRAErrors()).toBe(6);
82
+ expect(countMISRAErrors()).toBe(7);
70
83
  expect(countMISRAErrors(Query.search(FileJp, { name: "bad1.c" }).first()!)).toBe(1);
71
84
  expect(countMISRAErrors(Query.search(FileJp, { name: "bad2.c" }).first()!)).toBe(1);
72
- expect(countMISRAErrors(Query.search(FileJp, { name: "bad3.c" }).first()!)).toBe(4);
85
+ expect(countMISRAErrors(Query.search(FileJp, { name: "bad3.c" }).first()!)).toBe(5);
73
86
  expect(countMISRAErrors(Query.search(FileJp, { name: "good.c" }).first()!)).toBe(0);
74
87
  });
75
88
 
@@ -80,6 +93,6 @@ describe("Rule 17.4", () => {
80
93
  const configFilename = "misra_config.json";
81
94
  const configFilePath = path.join(__dirname, configFilename);
82
95
 
83
- expect(countErrorsAfterCorrection(configFilePath)).toBe(1);
96
+ expect(countErrorsAfterCorrection(configFilePath)).toBe(2);
84
97
  });
85
98
  });
@@ -1,10 +1,19 @@
1
1
  {
2
- "defaultValues": {
3
- "int": 0,
4
- "unsigned int": 0,
5
- "float": 0.0,
6
- "enum Status": "SUCCESS",
7
- "Color": "RED",
8
- "my_int_type": 0
9
- }
2
+ "defaultValues": {
3
+ "int": 0,
4
+ "unsigned int": 0,
5
+ "float": 0.0,
6
+ "enum Status": "SUCCESS",
7
+ "Color": "RED",
8
+ "Size": "MEDIUM",
9
+ "my_int_type": 0
10
+ },
11
+ "implicitCalls": {
12
+ "printf": "stdio.h",
13
+ "print": "stdio.h",
14
+ "sin": "math.h",
15
+ "pow": "math.h",
16
+ "half": "math.h",
17
+ "println": "stdio.h"
18
+ }
10
19
  }
@@ -3,15 +3,16 @@ import Clava from "@specs-feup/clava/api/clava/Clava.js";
3
3
  import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
4
4
  import { FileJp, Program } from "@specs-feup/clava/api/Joinpoints.js";
5
5
  import Query from "@specs-feup/lara/api/weaver/Query.js";
6
+ import * as os from 'os';
6
7
 
7
8
  export function countMISRAErrors(startingPoint: FileJp | Program = Query.root() as Program): number {
8
9
  MISRATool.checkCompliance(startingPoint);
9
- return MISRATool.getMISRAErrors().length;
10
+ return MISRATool.getErrorCount();
10
11
  }
11
12
 
12
13
  export function countErrorsAfterCorrection(configPath?: string): number {
13
14
  MISRATool.applyCorrections(configPath);
14
- return MISRATool.getMISRAErrors().length;
15
+ return MISRATool.getActiveErrorCount();
15
16
  }
16
17
 
17
18
  export interface TestFile {
@@ -22,7 +23,18 @@ export interface TestFile {
22
23
 
23
24
  export function registerSourceCode(files: TestFile[]): void {
24
25
  beforeEach(() => {
25
- Clava.getData().setStandard(process.env.STD_VERSION!);
26
+ const dataStore = Clava.getData();
27
+
28
+ dataStore.setStandard(process.env.STD_VERSION!);
29
+
30
+ // If running on macOS, change libcCxxMode
31
+ if (os.platform() === 'darwin') {
32
+ const key = "libcCxxMode";
33
+ const allowedValues = dataStore.getType(key).getEnumConstants();
34
+ const systemValue = allowedValues.find((value: any) => value.name() === "SYSTEM");
35
+ dataStore.put(key, systemValue);
36
+ }
37
+
26
38
  Clava.getProgram().push();
27
39
  const program = Clava.getProgram();
28
40
  files.forEach(file => {
@@ -1,5 +1,6 @@
1
1
  import Query from "@specs-feup/lara/api/weaver/Query.js";
2
- import { Comment, Type, Case, Joinpoint, ArrayType, TypedefDecl, DeclStmt, TypedefNameDecl, StorageClass, FunctionJp, Vardecl, FileJp, RecordJp, EnumDecl, PointerType, Switch, BuiltinType, BinaryOp, Break, Scope, Statement, Expression, WrapperStmt, ElaboratedType, TagType, Param, Varref } from "@specs-feup/clava/api/Joinpoints.js";
2
+ import { Comment, Type, Case, Joinpoint, ArrayType, TypedefDecl, DeclStmt, TypedefNameDecl, StorageClass, FunctionJp, Vardecl, FileJp, RecordJp, EnumDecl, PointerType, Switch, BuiltinType, BinaryOp, Break, Scope, Statement, Expression, WrapperStmt, ElaboratedType, TagType, Param, Varref, Program, Include, Call } from "@specs-feup/clava/api/Joinpoints.js";
3
+ import ClavaJoinPoints from "@specs-feup/clava/api/clava/ClavaJoinPoints.js";
3
4
 
4
5
  /**
5
6
  * Checks if the comment is an inline comment
@@ -72,28 +73,21 @@ export function getExternals(): (FunctionJp | Vardecl)[] {
72
73
  * @returns true if the joinpoint has a defined type, otherwise false
73
74
  */
74
75
  export function hasDefinedType($jp: Joinpoint): boolean {
75
- return $jp.hasType && $jp.type !== null || $jp.type !== undefined;
76
+ return $jp.hasType && $jp.type !== null && $jp.type !== undefined;
76
77
  }
77
78
 
78
79
  /**
79
80
  * Retrieves the base type of the provided joinpoint.
80
81
  * @param $jp The joinpoint to retrieve its type
81
- * @returns The base type of the joinpoint, or undefined if the type is not defined
82
+ * @returns The base type of the joinpoint, or undefined if the joinpoint does not have a type
82
83
  */
83
84
  export function getBaseType($jp: Joinpoint): Type | undefined {
84
85
  if (!hasDefinedType($jp)) return undefined;
86
+ let jpType = $jp.type;
85
87
 
86
- let jpType: Type;
87
- if ($jp.type instanceof PointerType) {
88
- jpType = $jp.type.pointee;
89
- while (jpType instanceof PointerType) {
90
- jpType = jpType.pointee;
91
- }
92
- } else if ($jp.type instanceof ArrayType) {
93
- jpType = $jp.type.elementType;
94
- } else {
95
- jpType = $jp.type;
96
- }
88
+ while (jpType instanceof PointerType || jpType instanceof ArrayType) {
89
+ jpType = jpType instanceof PointerType ? jpType.pointee : jpType.elementType;
90
+ }
97
91
  return jpType;
98
92
  }
99
93
 
@@ -128,12 +122,9 @@ export function hasTypeDecl($jp: Joinpoint): boolean {
128
122
  */
129
123
  export function getTypedJps(startingPoint?: Joinpoint): Joinpoint[] {
130
124
  if (startingPoint) {
131
- return Query.searchFrom(startingPoint, Joinpoint).get().filter(jp => jp.hasType && jp.type !== null && jp.type !== undefined)
125
+ return Query.searchFrom(startingPoint, Joinpoint, (jp) => hasDefinedType(jp)).get();
132
126
  }
133
- return Query.search(Joinpoint).get().filter(jp =>
134
- jp.hasType &&
135
- jp.type !== null &&
136
- jp.type !== undefined);
127
+ return Query.search(Joinpoint, (jp) => hasDefinedType(jp)).get();
137
128
  }
138
129
 
139
130
  /**
@@ -199,9 +190,8 @@ export function getNumOfSwitchClauses($jp: Switch): number {
199
190
 
200
191
  /**
201
192
  * Checks if the provided switch statement has a Boolean condition
202
- * @param switchStmt - The switch statement to check
203
- * @returns
204
- * Returns true if the switch statement has a Boolean condition, otherwise false
193
+ * @param switchStmt The switch statement to check
194
+ * @returns Returns true if the switch statement has a Boolean condition, otherwise false
205
195
  */
206
196
  export function switchHasBooleanCondition(switchStmt: Switch): boolean {
207
197
  return switchStmt.condition instanceof BinaryOp ||
@@ -213,9 +203,78 @@ export function switchHasBooleanCondition(switchStmt: Switch): boolean {
213
203
 
214
204
  /**
215
205
  * Checks if the provided switch statement contains any conditional break
206
+ *
216
207
  * @param switchStmt - The switch statement to analyze
217
208
  * @returns Returns true if the switch statement contains a conditional break, otherwise false
218
209
  */
219
210
  export function switchHasConditionalBreak(switchStmt: Switch): boolean {
220
211
  return Query.searchFrom(switchStmt, Break, { currentRegion: region => region.astId !== switchStmt.astId, enclosingStmt: jp => jp.astId === switchStmt.astId }).get().length > 0;
221
- }
212
+ }
213
+
214
+ /**
215
+ * Checks if a file compiles correctly after adding a statement by rebuilding it.
216
+ * If rebuilding fails, the file is considered invalid with the new statement.
217
+ *
218
+ * @param fileJp - The file to validate.
219
+ */
220
+ export function isValidFile(fileJp: FileJp) : boolean {
221
+ const programJp = fileJp.parent as Program;
222
+ let copyFile = ClavaJoinPoints.fileWithSource(`temp_misra_${fileJp.name}`, fileJp.code, fileJp.relativeFolderpath);
223
+
224
+ copyFile = programJp.addFile(copyFile) as FileJp;
225
+ try {
226
+ const rebuiltFile = copyFile.rebuild();
227
+ const fileToRemove = Query.searchFrom(programJp, FileJp, {filepath: rebuiltFile.filepath}).first();
228
+ fileToRemove?.detach();
229
+ return true;
230
+ } catch(error) {
231
+ copyFile.detach();
232
+ return false;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Retrieves the list of header files included in the given file
238
+ *
239
+ * @param fileJp The file join point
240
+ * @returns An array of strings with the names of the includes
241
+ */
242
+ export function getIncludesOfFile(fileJp: FileJp): string[] {
243
+ return fileJp.includes.map(includeJp => includeJp.name);
244
+ }
245
+
246
+ /**
247
+ * Removes a specific include directive from the given file, if it exists
248
+ *
249
+ * @param includeName The name of the include to remove
250
+ * @param fileJp The file from which the include should be removed
251
+ */
252
+ export function removeIncludeFromFile(includeName: string, fileJp: FileJp) {
253
+ const include = Query.searchFrom(fileJp, Include, {name: includeName}).first();
254
+ include?.detach();
255
+ }
256
+
257
+ /**
258
+ * Check if the given joinpoint represents a call to an implicit function.
259
+ *
260
+ * @param callJp The call join point to analyze
261
+ */
262
+ export function isCallToImplicitFunction(callJp: Call): boolean {
263
+ return callJp.function.definitionJp === undefined && !callJp.function.isInSystemHeader;
264
+ }
265
+
266
+ /**
267
+ * Returns all files in the program that contain at least one call to an implicit function
268
+ *
269
+ * @param programJp - The program to analyze
270
+ * @returns A list of files with implicit function calls
271
+ */
272
+ export function getFilesWithCallToImplicitFunction(programJp: Program): FileJp[] {
273
+ const files = Query.searchFrom(programJp, FileJp).get();
274
+ return files.filter(
275
+ (fileJp) =>
276
+ Query.searchFrom(fileJp, Call, (callJp) =>
277
+ isCallToImplicitFunction(callJp)
278
+ ).get().length > 0
279
+ );
280
+ }