All files / TRANSPILE/1-Analyze CriticalSectionAnalyzer.ts

100% Statements 15/15
100% Branches 2/2
100% Functions 5/5
100% Lines 14/14

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;