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 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | 46x 466x 466x 466x 466x 1786x 1786x 1786x 1786x 1786x 1786x 1786x 673x 1786x 46x 46x 46x 159x 159x 159x 159x 159x 159x 142x 142x 71x 37x 47x 7x 142x 142x 81x 142x 142x 43x 43x 832x 832x 183x 832x 832x 832x 183x 10x 2x 2x 2x 2x 1x 183x 61x 61x 81x 77x 61x 61x 9x 183x 17x 17x 466x 466x 466x 466x 159x 466x 6x | /**
* ADR-007 bit indexing: E0856, E0888.
*
* #1322. Two throws, both reported as `1:0` after building a position into
* their own message text (`Error at line N: …`) -- a position carried as prose
* rather than by the diagnostic.
*
* ## E0856: how many subscripts a base allows
*
* ADR-036 gives an array one subscript per dimension; ADR-007 gives a
* bit-indexable scalar one more, for the bit. So `arr[i][b]` is the last legal
* form for a one-dimensional array and `arr[i][b][x]` indexes a value that is
* not an array.
*
* Only integer and float bases are checked, as codegen did: a string is a char
* array with its own rules, a bitmap rejects bracket indexing under E0883, and
* a struct is reached through member access instead.
*
* ## E0888: a float bit range needs somewhere to put the union
*
* Reading `f[start, width]` on a float is lowered to a union copy (MISRA
* C:2012 Rule 21.15 forbids the pointer cast), and a union copy is a
* statement. At file scope there is no statement to emit it into. This is the
* one rule here that is about WHERE the access is written rather than what it
* is written on.
*
* ## E0890: a read-modify-write evaluates its target twice
*
* A bit, bit-range or bitmap-field write keeps the other bits, so it reads the
* target and stores it back: every subscript in the target, and the bit index
* with it, is evaluated twice. A call or a volatile read there runs twice, and
* the store can land on a different element than the read. Owner ruling
* (#1760 review): reject it, as E0702 rejects a call in a condition. A
* write-1 register member is composed without a read (ADR-004), so it is
* evaluated once and is not restricted.
*/
import { ParserRuleContext, ParseTreeWalker } from "antlr4ng";
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import TYPE_WIDTH from "../../types/TYPE_WIDTH";
import ParserUtils from "../../utils/ParserUtils";
import OperandTyper from "../../utils/OperandTyper";
import ChainRoot from "../../utils/ChainRoot";
import EnclosingFunction from "./helpers/EnclosingFunction";
import AssignmentSiteListener from "./AssignmentSiteListener";
import IBitAccessError from "./types/IBitAccessError";
import TChainRoot from "../../types/TChainRoot";
import RegisterAccessMode from "../../utils/RegisterAccessMode";
import RegisterMemberReference from "./helpers/RegisterMemberReference";
import type TAssignmentSite from "./types/TAssignmentSite";
import SHARED_FLOAT_TYPES from "../../types/FLOAT_TYPES";
import type IAnalysisContext from "./types/IAnalysisContext";
/**
* The floats a bit range is lowered through a union for.
*
* #1450: built from ADR-024's shared list rather than respelling it. The two
* agreed, which is exactly why nothing would have failed when a new float width
* was added to one of them.
*/
const FLOAT_TYPES = new Set<string>(SHARED_FLOAT_TYPES);
class BitAccessListener extends CNextListener {
private readonly found: IBitAccessError[] = [];
public constructor(private readonly context: IAnalysisContext) {
super();
}
public errors(): IBitAccessError[] {
return this.found;
}
override enterPostfixExpression = (
ctx: Parser.PostfixExpressionContext,
): void => {
const primary = ctx.primaryExpression();
Iif (!primary) return;
// `this.x[…]` names the declaration in its first op; a bare `x[…]` in the
// primary. Anything else is not a name this rule can measure.
const ops = ctx.postfixOp();
const root = ChainRoot.ofPrimary(primary);
const name = BitAccessListener.nameOf(primary, ops, root);
if (name === undefined) return;
this.checkChain(name, ops.slice(root === null ? 0 : 1), ctx, root);
};
/**
* The declared name a postfix chain leads with. A rooted chain spends its
* primary on the keyword and puts the name in the first op, so the two
* shapes read from different offsets -- and a rooted chain whose first op is
* a subscript rather than a `.name` names nothing this rule can measure.
*/
private static nameOf(
primary: Parser.PrimaryExpressionContext,
ops: readonly Parser.PostfixOpContext[],
root: TChainRoot,
): string | undefined {
if (root === null) return primary.IDENTIFIER()?.getText();
const first = ops[0];
Iif (first === undefined || first.DOT() === null) return undefined;
return first.IDENTIFIER()?.getText();
}
/**
* A target is not an expression. `flags[4][3] <- 5` is an
* `assignmentTarget`, whose ops are `postfixTargetOp` -- a different node
* type that `enterPostfixExpression` never sees. Both fixtures for E0856 are
* writes, so a rule reading only expressions caught neither of them.
*/
public checkSite(site: TAssignmentSite): void {
const target = site.assignmentTarget();
const name = target.IDENTIFIER()?.getText();
Iif (name === undefined) return;
// A target carries its root as its own token, so the name is always the
// target's IDENTIFIER and no op is consumed.
this.checkChain(
name,
target.postfixTargetOp(),
target,
ChainRoot.ofTarget(target),
);
this.checkSingleEvaluation(site);
}
/** E0890: each subscript of a read-modify-write target with a side effect */
private checkSingleEvaluation(site: TAssignmentSite): void {
// A compound operator on a bit is E0857's; on a whole location it is one
// C compound assignment, which evaluates its target once
if (site.assignmentOperator().getText() !== "<-") return;
const target = site.assignmentTarget();
if (!this.isReadModifyWrite(target)) return;
const indices = target.postfixTargetOp().flatMap((op) => op.expression());
for (const index of indices) {
if (!OperandTyper.hasSideEffect(index, this.context)) continue;
this.report(
index,
"E0890",
`'${index.getText()}' would be evaluated twice: '${target.getText()}' is read and then written back`,
"A bit, bit-range or bitmap-field write keeps the other bits, so it reads its target and stores it back, and every subscript in the target runs twice. Store the index in a variable first (ADR-007).",
);
}
}
/** Whether writing `target` reads it back first to keep the other bits */
private isReadModifyWrite(target: Parser.AssignmentTargetContext): boolean {
const last = OperandTyper.chainOf(target, this.context).steps.at(-1);
if (last === undefined) return false;
const writesBits =
last.subscript === "bit_single" || last.subscript === "bit_range";
const writesBitmapField =
last.subscript === null && (last.before?.bitmapTypeName ?? null) !== null;
if (!writesBits && !writesBitmapField) return false;
const register = RegisterMemberReference.ofTarget(target, this.context);
return !RegisterAccessMode.isWriteOne(register?.access);
}
private checkChain(
name: string,
subscripts: readonly (
| Parser.PostfixOpContext
| Parser.PostfixTargetOpContext
)[],
at: Parser.PostfixExpressionContext | Parser.AssignmentTargetContext,
root: TChainRoot,
): void {
// The declaration the spelling names, as the typer binds it: the type
// its first subscript applies to (#1668). A `this.` root has spent its
// `.name`, so the typer's steps begin at the subscripts in both shapes.
const declared =
OperandTyper.chainOf(at, this.context).steps[0]?.before ?? null;
if (declared === null) return; // not a declaration this pass can measure
const spelling = root === null ? name : `${root}.${name}`;
const baseType = declared.typeName ?? "";
this.checkFloatRangeScope(subscripts, baseType, spelling, at);
this.checkDepth(
subscripts,
declared.dimensions.length,
baseType,
spelling,
at,
);
}
/**
* E0888: a float bit RANGE read outside a function body. A single bit index
* is not lowered through a union, so it is not restricted.
*/
private checkFloatRangeScope(
subscripts: readonly (
| Parser.PostfixOpContext
| Parser.PostfixTargetOpContext
)[],
baseType: string,
name: string,
at: ParserRuleContext,
): void {
if (!FLOAT_TYPES.has(baseType)) return;
if (EnclosingFunction.of(at) !== null) return;
const range = subscripts.find(
(op) => op.LBRACKET() !== null && op.expression().length === 2,
);
if (range === undefined) return;
const bounds = range.expression().map((e) => e.getText());
this.report(
at,
"E0888",
`Float bit range '${name}[${bounds.join(", ")}]' cannot be read at file scope`,
"Reading a float's bits copies it through a union (MISRA C:2012 Rule 21.15 forbids the pointer cast), and a copy is a statement -- there is none at file scope. Read it inside a function.",
);
}
/**
* E0856: ADR-036 allows one subscript per array dimension and ADR-007 one
* more for a bit index. Only a bit-indexable scalar element has that extra
* one, which is why the base type decides.
*/
private checkDepth(
subscripts: readonly (
| Parser.PostfixOpContext
| Parser.PostfixTargetOpContext
)[],
dimensions: number,
baseType: string,
name: string,
at: ParserRuleContext,
): void {
if (TYPE_WIDTH[baseType] === undefined) return; // not a bit-indexable scalar
let leading = 0;
for (const op of subscripts) {
if (op.LBRACKET() === null) break;
leading += 1;
}
const allowed = dimensions + 1;
if (leading <= allowed) return;
const shape =
dimensions === 0
? `a scalar '${baseType}'`
: `a ${dimensions}-dimensional '${baseType}' array`;
this.report(
at,
"E0856",
`too many subscripts on '${name}': it is ${shape}, so it allows at most ${allowed}`,
`${dimensions} for the array ${dimensions === 1 ? "dimension" : "dimensions"} plus one optional bit index (ADR-036/ADR-007). Indexing further indexes a value that is not an array. Did you mean the bit range '${name}[start, width]'?`,
);
}
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 BitAccessAnalyzer {
/** #1456: handed in rather than read off shared state. */
constructor(private readonly context: IAnalysisContext) {}
public analyze(tree: Parser.ProgramContext): IBitAccessError[] {
const listener = new BitAccessListener(this.context);
ParseTreeWalker.DEFAULT.walk(listener, tree);
ParseTreeWalker.DEFAULT.walk(
new AssignmentSiteListener((site) => listener.checkSite(site)),
tree,
);
// Reported in source order, as the one walk these replace did
return listener
.errors()
.sort((a, b) => a.line - b.line || a.column - b.column);
}
}
export default BitAccessAnalyzer;
|