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 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | 46x 469x 469x 1745x 1745x 1745x 2x 4x 4x 4x 2x 4x 4x 4x 1x 1x 3x 14x 14x 1x 1x 18x 18x 6x 4x 4x 12x 4x 8x 18x 18x 18x 18x 18x 18x 17x 17x 17x 8x 9x 9x 9x 9x 17x 17x 17x 7x 34x 32x 32x 7x 7x 4x 1x 1x 1x 11x 11x 469x 469x 469x | /**
* ADR-068 loops and ADR-026 break/continue: E0703, E0705, E0707.
*
* #1322. Four throws in `output/` -- `CodeGenerator` for `break`/`continue`,
* `ControlFlowGenerator` for `for (;;)` and for `forever` in a non-void
* function, `TypeValidator` for an always-true literal condition -- three of
* them reaching the user as `1:0`. Every one is a fact of the parse tree:
* which keyword was written, whether a `for` header has a condition, what the
* enclosing function's declared type is, and what two literals compare to.
*
* ## The always-true slice is the LITERAL slice, as it was
*
* `while (1 = 1)`, `5 > 3`, `true = true`, `1 != 2`: a single comparison of
* two integer or boolean literals. Named constants, compound conditions,
* floats, and always-FALSE conditions are #1076's (the full MISRA 14.3 effort)
* and are deliberately not decided here; this pass moves the rule, it does not
* widen it. A leading-zero integer (`0777`) is emitted verbatim and read by C
* as OCTAL, so it is skipped rather than parsed as decimal -- the verdict has
* to agree with the generated code's value.
*
* E0701 (a condition must be a comparison) runs earlier in the same pass and
* halts, so a condition reaching the always-true check is already a
* comparison -- the ordering codegen relied on, kept by the step order.
*/
import { ParserRuleContext, ParseTreeWalker } from "antlr4ng";
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import REJECTED_KEYWORDS from "./REJECTED_KEYWORDS";
import LiteralUtils from "../../utils/LiteralUtils";
import ParserUtils from "../../utils/ParserUtils";
import ILoopError from "./types/ILoopError";
const FOREVER_HELP = "write 'forever { ... }' for an intentional infinite loop";
/** A single comparison of two compile-time literals. */
interface ILiteralComparison {
readonly operator: string;
readonly left: number;
readonly right: number;
}
class LoopListener extends CNextListener {
private readonly found: ILoopError[] = [];
public errors(): ILoopError[] {
return this.found;
}
/** E0703: `break;` and `continue;` parse as a bare identifier expression. */
override enterPrimaryExpression = (
ctx: Parser.PrimaryExpressionContext,
): void => {
const id = ctx.IDENTIFIER()?.getText();
if (id === undefined || !REJECTED_KEYWORDS.has(id)) return;
this.report(
ctx,
"E0703",
`'${id}' is not supported in C-Next - use structured conditions instead`,
"ADR-026 rejects `break` and `continue`: an early exit hides the loop's real condition. Fold the exit into the loop condition, or split the body into a function that returns.",
);
};
/** E0705: `forever` never yields a value, so only a void function may hold one. */
override enterForeverStatement = (
ctx: Parser.ForeverStatementContext,
): void => {
const returnType = LoopListener.enclosingFunctionType(ctx);
if (returnType === null || returnType === "void") return;
this.report(
ctx,
"E0705",
"forever loop in non-void function",
"a forever loop never returns a value; make the function return void, or use a while loop with an exit condition",
);
};
/** E0707: `for (;;)` is an infinite loop with the wrong spelling. */
override enterForStatement = (ctx: Parser.ForStatementContext): void => {
const condition = ctx.expression();
if (condition === null) {
this.report(
ctx,
"E0707",
"for-loop has no controlling expression (infinite loop)",
FOREVER_HELP,
);
return;
}
this.checkAlwaysTrue(condition);
};
override enterWhileStatement = (ctx: Parser.WhileStatementContext): void => {
this.checkAlwaysTrue(ctx.expression());
};
override enterDoWhileStatement = (
ctx: Parser.DoWhileStatementContext,
): void => {
this.checkAlwaysTrue(ctx.expression());
};
/** E0707: an always-true literal comparison as a loop condition. */
private checkAlwaysTrue(condition: Parser.ExpressionContext): void {
const comparison = LoopListener.asSingleLiteralComparison(condition);
if (comparison === null || !LoopListener.isAlwaysTrue(comparison)) return;
this.report(
condition,
"E0707",
`loop condition '${condition.getText()}' is always true`,
FOREVER_HELP,
);
}
/** The declared type of the function a statement sits in, or null outside one. */
private static enclosingFunctionType(node: ParserRuleContext): string | null {
let cursor: ParserRuleContext | null = node.parent;
while (cursor) {
if (cursor instanceof Parser.FunctionDeclarationContext) {
return cursor.type().getText();
}
cursor = cursor.parent;
}
return null;
}
/**
* `ctx` as a single comparison of two literal operands -- no `||`/`&&`, no
* ternary, no chaining -- or null. Anything involving identifiers, floats or
* sub-expressions is left to #1076.
*/
private static asSingleLiteralComparison(
ctx: Parser.ExpressionContext,
): ILiteralComparison | null {
const orExprs = ctx.ternaryExpression().orExpression();
Iif (orExprs.length !== 1) return null;
const andExprs = orExprs[0].andExpression();
Iif (andExprs.length !== 1) return null;
const equalityExprs = andExprs[0].equalityExpression();
if (equalityExprs.length !== 1) return null;
const equality = equalityExprs[0];
const relationalExprs = equality.relationalExpression();
if (relationalExprs.length === 2) {
// relExpr ('=' | '!=') relExpr
return LoopListener.literalComparison(
equality.getChild(1)?.getText(),
relationalExprs[0].getText(),
relationalExprs[1].getText(),
);
}
Eif (relationalExprs.length === 1) {
const operands = relationalExprs[0].bitwiseOrExpression();
Eif (operands.length === 2) {
// orExpr ('<' | '>' | '<=' | '>=') orExpr
return LoopListener.literalComparison(
relationalExprs[0].getChild(1)?.getText(),
operands[0].getText(),
operands[1].getText(),
);
}
}
return null;
}
private static literalComparison(
operator: string | undefined,
leftText: string,
rightText: string,
): ILiteralComparison | null {
const left = LoopListener.literalValue(leftText);
const right = LoopListener.literalValue(rightText);
if (operator === undefined || left === null || right === null) return null;
return { operator, left, right };
}
/**
* Strict literal-to-number: integer literals (decimal, hex, binary, with an
* optional type suffix) and `true`/`false`. Everything else is not
* compile-time-known here.
*/
private static literalValue(text: string): number | null {
if (text === "true") return 1;
Iif (text === "false") return 0;
return LiteralUtils.integerValue(text);
}
private static isAlwaysTrue(comparison: ILiteralComparison): boolean {
const { left, right } = comparison;
switch (comparison.operator) {
case "=":
return left === right;
case "!=":
return left !== right;
case "<":
return left < right;
case ">":
return left > right;
case "<=":
return left <= right;
case ">=":
return left >= right;
default:
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 LoopAnalyzer {
public analyze(tree: Parser.ProgramContext): ILoopError[] {
const listener = new LoopListener();
ParseTreeWalker.DEFAULT.walk(listener, tree);
return listener.errors();
}
}
export default LoopAnalyzer;
|