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 199 200 201 202 203 204 205 206 207 208 209 210 211 | 328x 328x 410x 410x 371x 39x 39x 39x 39x 39x 39x 39x 7x 328x 328x 328x 1328x 1328x 1328x 1279x 49x 50x 50x 50x 50x 14x 36x 36x 36x 22x 36x 36x 1x 35x 35x 35x 35x 22x 13x 13x 12x 12x 1x 328x 328x 328x 328x 328x 328x 328x 328x 22x 22x 22x 22x 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)
*
* Issue #1220: the const-zero pass walks only THIS file's parse tree, so a
* const arriving through an #include was invisible and the check silently
* passed. Cross-file consts are resolved through CodeGenState, which reads the
* one const-value derivation on SymbolTable.
*/
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";
import CodeGenState from "../../state/CodeGenState";
/**
* 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. The local pass
// covers consts declared in this file (including function-local ones, which
// never reach the symbol table); CodeGenState covers the rest.
//
// Issue #1220: without the second half, `10 / ZERO` with an imported ZERO
// passed silently and emitted a real runtime division by zero.
const identifier = primaryExpr.IDENTIFIER();
if (identifier) {
const name = identifier.getText();
return (
this.constZeros.has(name) || CodeGenState.getCNextConstValue(name) === 0
);
}
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;
|