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;
|