All files / transpiler/logic/analysis DivisionByZeroAnalyzer.ts

93.44% Statements 57/61
88.23% Branches 30/34
100% Functions 8/8
94.91% Lines 56/59

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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198                                                167x     167x             140x       140x 131x     9x 9x       9x 9x 9x         9x 9x 2x                           167x 167x 167x             300x       300x 300x 273x       27x 28x 28x   28x 28x 2x     26x 26x     26x 17x                   26x 26x 1x       25x 25x         25x 25x 20x       5x 5x 4x 4x     1x               167x                 167x     167x 167x 167x     167x 167x   167x             17x 17x 17x   17x                               1x          
/**
 * Division By Zero Analyzer
 * Detects division and modulo by zero at compile time (ADR-051)
 *
 * Implemented Phases:
 * ✓ Phase 1: Literal zero detection (10 / 0, 10 % 0)
 * ✓ Phase 3: Const zero detection (const u32 ZERO <- 0; x / ZERO)
 *
 * Future Enhancement (Phase 3+):
 * - Const expression evaluation (const u32 VALUE <- 5 - 5; x / VALUE)
 */
 
import { ParseTreeWalker } from "antlr4ng";
import { CNextListener } from "../parser/grammar/CNextListener";
import * as Parser from "../parser/grammar/CNextParser";
import IDivisionByZeroError from "./types/IDivisionByZeroError";
import LiteralUtils from "../../../utils/LiteralUtils";
import ExpressionUtils from "../../../utils/ExpressionUtils";
import ParserUtils from "../../../utils/ParserUtils";
 
/**
 * First pass: Collect const declarations that are zero
 */
class ConstZeroCollector extends CNextListener {
  private readonly constZeros: Set<string> = new Set();
 
  public getConstZeros(): Set<string> {
    return this.constZeros;
  }
 
  /**
   * Track const declarations
   * variableDeclaration: atomicModifier? volatileModifier? constModifier? ...
   */
  override enterVariableDeclaration = (
    ctx: Parser.VariableDeclarationContext,
  ): void => {
    // Only process const declarations
    if (!ctx.constModifier()) {
      return;
    }
 
    const identifier = ctx.IDENTIFIER();
    Iif (!identifier) {
      return;
    }
 
    const name = identifier.getText();
    const expr = ctx.expression();
    Iif (!expr) {
      return;
    }
 
    // Check if the expression is a literal zero
    const literal = ExpressionUtils.extractLiteral(expr);
    if (literal && LiteralUtils.isZero(literal)) {
      this.constZeros.add(name);
    }
  };
}
 
/**
 * Second pass: Detect division by zero (including const identifiers)
 */
class DivisionByZeroListener extends CNextListener {
  private readonly analyzer: DivisionByZeroAnalyzer;
  // eslint-disable-next-line @typescript-eslint/lines-between-class-members
  private readonly constZeros: Set<string>;
 
  constructor(analyzer: DivisionByZeroAnalyzer, constZeros: Set<string>) {
    super();
    this.analyzer = analyzer;
    this.constZeros = constZeros;
  }
 
  /**
   * Check multiplicative expressions for division/modulo by zero
   * multiplicativeExpression: unaryExpression (('*' | '/' | '%') unaryExpression)*
   */
  override enterMultiplicativeExpression = (
    ctx: Parser.MultiplicativeExpressionContext,
  ): void => {
    // Get all unary expressions
    const operands = ctx.unaryExpression();
    if (operands.length < 2) {
      return; // No operator, just a single operand
    }
 
    // Check each operator and its right operand
    for (let i = 0; i < operands.length - 1; i++) {
      const operatorToken = ctx.getChild(i * 2 + 1); // Operators are at odd indices
      Iif (!operatorToken) continue;
 
      const operator = operatorToken.getText();
      if (operator !== "/" && operator !== "%") {
        continue; // Only check division and modulo
      }
 
      const rightOperand = operands[i + 1];
      const { line, column } = ParserUtils.getPosition(rightOperand);
 
      // Check if right operand is zero
      if (this.isZero(rightOperand)) {
        this.analyzer.addError(operator, line, column);
      }
    }
  };
 
  /**
   * Check if a unary expression evaluates to zero
   */
  private isZero(ctx: Parser.UnaryExpressionContext): boolean {
    // Get the postfix expression
    const postfixExpr = ctx.postfixExpression();
    if (!postfixExpr) {
      return false;
    }
 
    // Get the primary expression
    const primaryExpr = postfixExpr.primaryExpression();
    Iif (!primaryExpr) {
      return false;
    }
 
    // Check if it's a literal
    const literal = primaryExpr.literal();
    if (literal) {
      return LiteralUtils.isZero(literal);
    }
 
    // Check if it's a const identifier that evaluates to zero
    const identifier = primaryExpr.IDENTIFIER();
    if (identifier) {
      const name = identifier.getText();
      return this.constZeros.has(name);
    }
 
    return false;
  }
}
 
/**
 * Analyzer that detects division by zero
 */
class DivisionByZeroAnalyzer {
  private errors: IDivisionByZeroError[] = [];
 
  /**
   * Analyze the parse tree for division by zero
   * Two-pass analysis:
   * 1. Collect const declarations that are zero
   * 2. Detect division/modulo by literal zero or const zero
   */
  public analyze(tree: Parser.ProgramContext): IDivisionByZeroError[] {
    this.errors = [];
 
    // First pass: collect const zeros
    const collector = new ConstZeroCollector();
    ParseTreeWalker.DEFAULT.walk(collector, tree);
    const constZeros = collector.getConstZeros();
 
    // Second pass: detect division by zero
    const listener = new DivisionByZeroListener(this, constZeros);
    ParseTreeWalker.DEFAULT.walk(listener, tree);
 
    return this.errors;
  }
 
  /**
   * Add a division by zero error
   */
  public addError(operator: string, line: number, column: number): void {
    const isDivision = operator === "/";
    const code = isDivision ? "E0800" : "E0802";
    const opName = isDivision ? "Division" : "Modulo";
 
    this.errors.push({
      code,
      operator,
      line,
      column,
      message: `${opName} by zero`,
      helpText: isDivision
        ? "Consider using safe_div(output, numerator, divisor, defaultValue) for runtime safety"
        : "Consider using safe_mod(output, numerator, divisor, defaultValue) for runtime safety",
    });
  }
 
  /**
   * Get all detected errors
   */
  public getErrors(): IDivisionByZeroError[] {
    return this.errors;
  }
}
 
export default DivisionByZeroAnalyzer;