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                                                184x     184x             228x       228x 195x     33x 33x       33x 33x 33x         33x 33x 5x                           184x 184x 184x             721x       721x 721x 683x       38x 39x 39x   39x 39x 9x     30x 30x     30x 17x                   30x 30x 1x       29x 29x         29x 29x 20x       9x 9x 8x 8x     1x               184x                 184x     184x 184x 184x     184x 184x   184x             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;