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 | 46x 490x 490x 490x 490x 72x 72x 3x 3x 1x 1x 4x 4x 4x 1618x 1618x 4x 4x 79x 4x 4x 75x 75x 1x 1x 1x 74x 4x 4x 78x 78x 78x 80x 80x 80x 82x 27x 27x 27x 13x 13x 13x 13x 13x 4x 4x 13x 13x 13x 5x 8x 490x 490x 490x 490x | /**
* ADR-022's controlling-expression rule: E0701 and E0702.
*
* #1322. Four throws in `TypeValidator`, all reaching the user as `1:0`, across
* nineteen fixtures that assert that position.
*
* The rule itself is purely SYNTACTIC -- every leaf operand of a controlling
* expression, after decomposing `||` and `&&`, must be an equality or
* relational comparison. It reads the parse tree and nothing else, which makes
* this the second family in a row that needed no capability 2.1 did not already
* have. Only the help text asks a type question, and it asks it of the lexical
* frames rather than of codegen's registry.
*
* ## The enumeration, named because it is the risk
*
* Five productions carry a controlling expression: `if`, `while`, `do-while`,
* `for` and the ternary. There is no way to ask the grammar "which rules have
* one" -- they are five listener methods, and a sixth production added later
* would be silently unchecked. That is exactly how E0853 came to miss `switch`.
*
* Two things are done about it rather than hoped about it. The five delegate to
* ONE check, so only the collection is enumerated and never the decision. And
* `controlling-expression-every-kind-error` exercises all five in one file, so
* its expected output carries five diagnostics: a kind that stops being visited
* drops the count and fails the fixture. `forever` is deliberately absent from
* the list -- it has no controlling expression, which is the point of it.
*/
import { ParserRuleContext, ParseTreeWalker } from "antlr4ng";
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import ExpressionUtils from "../../utils/ExpressionUtils";
import ParserUtils from "../../utils/ParserUtils";
import OperandTyper from "../../utils/OperandTyper";
import IControllingExpressionError from "./types/IControllingExpressionError";
import type IAnalysisContext from "./types/IAnalysisContext";
const CALL_HELP = "Store the function result in a variable first.";
class ControllingExpressionListener extends CNextListener {
private readonly found: IControllingExpressionError[] = [];
public constructor(private readonly context: IAnalysisContext) {
super();
}
public errors(): IControllingExpressionError[] {
return this.found;
}
// --- The five productions that carry a controlling expression -------------
// Each one only NAMES its expression; the rule itself lives in `check`.
override enterIfStatement = (ctx: Parser.IfStatementContext): void => {
this.check(ctx.expression(), "if");
};
override enterWhileStatement = (ctx: Parser.WhileStatementContext): void => {
this.check(ctx.expression(), "while");
};
override enterDoWhileStatement = (
ctx: Parser.DoWhileStatementContext,
): void => {
this.check(ctx.expression(), "do-while");
};
override enterForStatement = (ctx: Parser.ForStatementContext): void => {
// `for (;;)` has no controlling expression at all -- that is E0707's case
// (a disguised infinite loop), not this rule's.
const condition = ctx.expression();
if (condition) this.check(condition, "for");
};
override enterTernaryExpression = (
ctx: Parser.TernaryExpressionContext,
): void => {
if (ctx.COLON() === null) return;
const condition = ctx.orExpression(0);
// Addressed through `orExpression(0)`, never `getChild(0)`: the condition
// is parenthesised, so child 0 is the `(` (CLAUDE.md).
Eif (condition) this.checkOrExpression(condition, "ternary");
};
// --- The one decision -----------------------------------------------------
private check(ctx: Parser.ExpressionContext, kind: string): void {
if (ExpressionUtils.hasFunctionCall(ctx)) {
this.reportCall(ctx, kind);
return;
}
const orExprs = ctx.ternaryExpression().orExpression();
if (orExprs.length !== 1) {
// The controlling expression is itself a ternary.
const { line, column } = ParserUtils.getPosition(ctx);
this.found.push({
code: "E0701",
line,
column,
message: `${kind} condition must be a comparison, not a ternary`,
helpText:
"MISRA C:2012 Rule 14.4: lift the ternary into a variable and compare that.",
});
return;
}
this.checkComparison(orExprs[0], kind);
}
private checkOrExpression(
ctx: Parser.OrExpressionContext,
kind: string,
): void {
Iif (ExpressionUtils.hasFunctionCallInOr(ctx)) {
this.reportCall(ctx, kind);
return;
}
this.checkComparison(ctx, kind);
}
/**
* Every leaf operand, after decomposing `||` and `&&`, has to carry a
* comparison operator of its own.
*
* A bare value, a bare bool, a literal or a negation is rejected: `!ready` is
* not a comparison, and MISRA C:2012 Rule 14.4 wants the intent written out.
*/
private checkComparison(
orExpr: Parser.OrExpressionContext,
kind: string,
): void {
const andExprs = orExpr.andExpression();
Iif (andExprs.length === 0) {
this.reportNotComparison(orExpr, kind);
return;
}
for (const andExpr of andExprs) {
const equalityExprs = andExpr.equalityExpression();
Iif (equalityExprs.length === 0) {
this.reportNotComparison(orExpr, kind);
return;
}
for (const equalityExpr of equalityExprs) {
// An equality operator (`=`, `!=`) makes this operand a comparison.
if (equalityExpr.relationalExpression().length > 1) continue;
const relational = equalityExpr.relationalExpression(0);
Iif (!relational) {
this.reportNotComparison(orExpr, kind);
return;
}
// A relational operator (`<`, `>`, `<=`, `>=`) does too.
if (relational.bitwiseOrExpression().length > 1) continue;
this.reportNotComparison(equalityExpr, kind);
return;
}
}
}
private reportNotComparison(
node: Parser.OrExpressionContext | Parser.EqualityExpressionContext,
kind: string,
): void {
const text = node.getText();
const { line, column } = ParserUtils.getPosition(node);
this.found.push({
code: "E0701",
line,
column,
message: `${kind} condition must be a comparison, not '${text}'`,
helpText: `MISRA C:2012 Rule 14.4: ${this.suggestionFor(node, text)}`,
});
}
private reportCall(
node: Parser.ExpressionContext | Parser.OrExpressionContext,
kind: string,
): void {
const { line, column } = ParserUtils.getPosition(node);
this.found.push({
code: "E0702",
line,
column,
message: `${kind} condition may not call a function: '${node.getText()}'`,
helpText: `MISRA C:2012 Rule 13.5: ${CALL_HELP}`,
});
}
/**
* What to write instead.
*
* A `bool` gets `x = true` and anything else gets `x > 0`, so the suggestion
* is asked of the operand's DECLARED type. It reads the lexical frames rather
* than codegen's type registry -- the registry does not exist yet when this
* pass runs, and the frames also honour shadowing, which the registry's flat
* name lookup does not.
*/
private suggestionFor(node: ParserRuleContext, text: string): string {
const negated = text.startsWith("!");
const base = negated ? text.slice(1) : text;
if (
OperandTyper.isBoolean(OperandTyper.typeOfName(base, node, this.context))
) {
return `write it out, e.g. ${base} = ${negated ? "false" : "true"}`;
}
return `write it out, e.g. ${text} > 0 or ${text} != 0`;
}
}
class ControllingExpressionAnalyzer {
/** #1456: handed in rather than read off shared state. */
constructor(private readonly context: IAnalysisContext) {}
public analyze(tree: Parser.ProgramContext): IControllingExpressionError[] {
const listener = new ControllingExpressionListener(this.context);
ParseTreeWalker.DEFAULT.walk(listener, tree);
return listener.errors();
}
}
export default ControllingExpressionAnalyzer;
|