All files / TRANSPILE/1-Analyze SizeofAnalyzer.ts

100% Statements 28/28
100% Branches 26/26
100% Functions 6/6
100% Lines 25/25

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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126                                                                          465x     465x     12x                 12x     12x   12x     12x 4x 4x 1x           1x       11x 4x                               111x 9x   107x 113x 113x       52x     55x                 5x 5x           465x 465x 465x          
/**
 * ADR-023's `sizeof` operand: E0601, E0602.
 *
 * #1322. Two throws in `SizeofResolver`, both carrying their code inside the
 * message text (`Error[E0601]: …`) and reaching the user as `1:0` -- a code
 * spelled into prose is not a code the tooling can see, which is why neither
 * appeared in `docs/error-codes.md` under its own row until now.
 *
 * ## E0601: the same spelling, opposite answers
 *
 * `sizeof(data)` measures the array when `data` is a local, and a POINTER when
 * it is a parameter -- ADR-006 passes arrays by reference, so the parameter is
 * a pointer whatever its declared dimensions say. The rule is therefore about
 * where the name is DECLARED, not about its type, which is why it asks
 * `EnclosingFunction.parameterOf` rather than the frames: a local array of the
 * same name and shape is fine.
 *
 * ## E0602: what a side effect actually is
 *
 * MISRA C:2012 Rule 13.6. Codegen tested the operand's TEXT for each of eleven
 * assignment operators before walking the tree for a call. Assignment is a
 * STATEMENT in this grammar, so none of those eleven could ever appear inside
 * a `sizeof` operand and all eleven were dead -- eleven lines that read as a
 * rule. A call is the only side effect an expression can have, and that is the
 * whole check here.
 */
 
import { ParserRuleContext, ParseTreeWalker } from "antlr4ng";
 
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import ExpressionUnwrapper from "../../utils/ExpressionUnwrapper";
import ParserUtils from "../../utils/ParserUtils";
import EnclosingFunction from "./helpers/EnclosingFunction";
import ISizeofError from "./types/ISizeofError";
 
class SizeofListener extends CNextListener {
  private readonly found: ISizeofError[] = [];
 
  public errors(): ISizeofError[] {
    return this.found;
  }
 
  override enterSizeofExpression = (
    ctx: Parser.SizeofExpressionContext,
  ): void => {
    // The grammar is `sizeof '(' (type | expression) ')'` and tries TYPE
    // first, so a bare name -- which is what `sizeof(data)` is -- arrives as a
    // `userType` rather than as an expression. Both branches are read here for
    // that reason: reading only `expression()` finds nothing for the exact
    // spelling E0601 exists to reject.
    const bareName =
      ctx.type()?.userType() !== null && ctx.type() !== null
        ? ctx.type()!.getText()
        : null;
    const expr = ctx.expression();
    const name =
      bareName ??
      (expr === null ? null : ExpressionUnwrapper.getSimpleIdentifier(expr));
 
    if (name !== null) {
      const parameter = EnclosingFunction.parameterOf(name, ctx);
      if (parameter !== null && EnclosingFunction.isArrayParameter(parameter)) {
        this.report(
          ctx,
          "E0601",
          `sizeof() on array parameter '${name}' measures a pointer, not the array`,
          `An array parameter is passed by reference (ADR-006), so its size is the pointer's. Use ${name}.element_count for the count, or sizeof(elementType) * ${name}.element_count for the bytes.`,
        );
        return;
      }
    }
 
    if (expr !== null && SizeofListener.containsCall(expr)) {
      this.report(
        expr,
        "E0602",
        "sizeof() operand must not have side effects",
        "MISRA C:2012 Rule 13.6: `sizeof` does not evaluate its operand, so a call inside it never runs. Compute the value first and take the size of that.",
      );
    }
  };
 
  /**
   * Whether a call appears anywhere in the operand.
   *
   * A call is the only side effect an expression can have here: assignment is
   * a statement, and increment and decrement are not expressions either.
   */
  private static containsCall(node: ParserRuleContext): boolean {
    if (node instanceof Parser.PostfixExpressionContext) {
      if (node.postfixOp().some((op) => op.LPAREN() !== null)) return true;
    }
    for (let index = 0; index < node.getChildCount(); index += 1) {
      const child = node.getChild(index);
      if (
        child instanceof ParserRuleContext &&
        SizeofListener.containsCall(child)
      ) {
        return true;
      }
    }
    return false;
  }
 
  private report(
    at: ParserRuleContext,
    code: string,
    message: string,
    helpText: string,
  ): void {
    const { line, column } = ParserUtils.getPosition(at);
    this.found.push({ code, line, column, message, helpText });
  }
}
 
class SizeofAnalyzer {
  public analyze(tree: Parser.ProgramContext): ISizeofError[] {
    const listener = new SizeofListener();
    ParseTreeWalker.DEFAULT.walk(listener, tree);
    return listener.errors();
  }
}
 
export default SizeofAnalyzer;