Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | 473x 473x 473x 12x 12x 12x 12x 262x 262x 5x 5x 473x 473x 473x | /**
* E0853: `return` inside a `critical` block leaves interrupts disabled.
*
* The generated C brackets the block with `__cnx_get_PRIMASK()` /
* `__cnx_disable_irq()` on the way in and `__cnx_set_PRIMASK(__primask)` on the
* way out. A `return` jumps past the restore, so on device the interrupts stay
* off -- not a wrong value, a hung system.
*
* ## Why this moved, and what moving found
*
* #1322 relocates it from three throws in `TypeValidator`, which shared one
* message and were driven by a hand-rolled recursion:
* `_validateStatementForEarlyExit` enumerated the statement kinds it would
* descend into -- return, if, while, for, do-while.
*
* It did not list `switch`. So this compiled clean:
*
* critical {
* switch (value) {
* case 1 { return value; }
* }
* }
*
* and emitted a `return` sitting between `__cnx_disable_irq()` and
* `__cnx_set_PRIMASK()`. That is the exact hazard the rule exists to prevent,
* shipped past the guard that prevents it.
*
* A walk cannot have that hole, because it does not enumerate -- it visits.
* Any statement the grammar can nest inside a critical block is reached,
* including ones added later, without this file being edited.
*/
import { ParseTreeWalker } from "antlr4ng";
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import ParserUtils from "../../utils/ParserUtils";
import ICriticalSectionError from "./types/ICriticalSectionError";
class CriticalSectionListener extends CNextListener {
/**
* How many `critical` blocks are open. A counter rather than a boolean
* because `critical` nests, and a boolean cleared by the inner block's exit
* would stop applying while still inside the outer one.
*/
private depth = 0;
private readonly found: ICriticalSectionError[] = [];
public errors(): ICriticalSectionError[] {
return this.found;
}
override enterCriticalStatement = (): void => {
this.depth += 1;
};
override exitCriticalStatement = (): void => {
this.depth -= 1;
};
override enterReturnStatement = (
ctx: Parser.ReturnStatementContext,
): void => {
if (this.depth === 0) return;
const { line, column } = ParserUtils.getPosition(ctx);
this.found.push({
code: "E0853",
line,
column,
message:
"Cannot use 'return' inside critical section - would leave interrupts disabled",
helpText:
"The interrupt state is restored after the block; a `return` jumps past that restore. Assign the value to a variable inside the block and return it after the block closes.",
});
};
}
class CriticalSectionAnalyzer {
public analyze(tree: Parser.ProgramContext): ICriticalSectionError[] {
const listener = new CriticalSectionListener();
ParseTreeWalker.DEFAULT.walk(listener, tree);
return listener.errors();
}
}
export default CriticalSectionAnalyzer;
|