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 | 513x 513x 513x 522x 522x 46x 56x 152x 152x 77x 85x 141x 145x 145x 24x 24x 24x 145x 144x 92x 52x 52x 52x 20x 2x 29x 1x 52x 52x 513x 513x 513x 513x 513x 24x 1x | /**
* Array Index Type Analyzer
* Detects signed and floating-point types used as array or bit subscript indexes
*
* C-Next requires unsigned integer types for all subscript operations to prevent
* undefined behavior from negative indexes. This analyzer catches type violations
* at compile time with clear error messages.
*
* #1668 (C4e): each value leaf of an index is typed by the one operand typer,
* which binds a name where it is used. This kept one map of type TEXT per
* bare name for the whole file, filled by a walk before the check, so the
* last declaration of a name in the file typed every use of it: a `u32` loop
* index was rejected for another function's `i32 i`, and a real `i32` index
* accepted (#1694). A prefix operator (`-i`) was not typed at all.
*/
import { ParseTreeWalker } from "antlr4ng";
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import IArrayIndexTypeError from "./types/IArrayIndexTypeError";
import ParserUtils from "../../utils/ParserUtils";
import OperandTyper from "../../utils/OperandTyper";
import type IOperandType from "../../types/IOperandType";
import type IAnalysisContext from "./types/IAnalysisContext";
/**
* Validate subscript index expressions use unsigned integer types
*/
class IndexTypeListener extends CNextListener {
constructor(
private readonly analyzer: ArrayIndexTypeAnalyzer,
private readonly context: IAnalysisContext,
) {
super();
}
/**
* Check postfix operations in expressions (RHS: arr[idx], flags[bit])
*/
override enterPostfixOp = (ctx: Parser.PostfixOpContext): void => {
if (!ctx.LBRACKET()) return;
for (const expr of ctx.expression()) {
this.validateIndexExpression(expr);
}
};
/**
* Check postfix target operations in assignments (LHS: arr[idx] <- val)
*/
override enterPostfixTargetOp = (
ctx: Parser.PostfixTargetOpContext,
): void => {
if (!ctx.LBRACKET()) return;
for (const expr of ctx.expression()) {
this.validateIndexExpression(expr);
}
};
/**
* Validate that a subscript index uses an unsigned integer type: each value
* leaf of it, reported once, at the index.
*/
private validateIndexExpression(ctx: Parser.ExpressionContext): void {
for (const leaf of OperandTyper.valueLeaves(ctx, this.context)) {
const verdict = IndexTypeListener.verdictOf(leaf);
if (verdict === null) continue;
const { line, column } = ParserUtils.getPosition(ctx);
this.analyzer.addError(line, column, verdict.code, verdict.actualType);
return;
}
}
/**
* What a leaf's type makes it as an index: null when it is valid or
* cannot be typed (another diagnostic's to report), else the code.
*/
private static verdictOf(
leaf: IOperandType | null,
): { code: string; actualType: string } | null {
if (leaf === null) return null;
if (leaf.form.kind === "literal") {
// An integer or character literal is a valid index
return leaf.category === "floating"
? { code: "E0851", actualType: "float literal" }
: null;
}
const spelling = IndexTypeListener.spelling(leaf);
Iif (leaf.dimensions.length > 0)
return { code: "E0852", actualType: spelling };
switch (leaf.category) {
case "signed":
return { code: "E0850", actualType: spelling };
case "floating":
return { code: "E0851", actualType: spelling };
case "unsigned":
case "boolean":
case "enum":
// ADR-054: an enum transpiles to an unsigned constant
return null;
default:
// A struct, a string: E0852. A type the typer cannot name is passed
// over, as before
return leaf.typeName === null
? null
: { code: "E0852", actualType: spelling };
}
}
/** A type as the message names it: `i32`, `u8[4]` */
private static spelling(leaf: IOperandType): string {
const dimensions = leaf.dimensions.map((d) => `[${d}]`).join("");
return `${leaf.typeName ?? leaf.category}${dimensions}`;
}
}
/**
* Analyzer that detects non-unsigned-integer types used as subscript indexes
*/
class ArrayIndexTypeAnalyzer {
/** #1456: handed in rather than read off shared state. */
constructor(private readonly context: IAnalysisContext) {}
private errors: IArrayIndexTypeError[] = [];
/**
* Analyze the parse tree for invalid subscript index types
*/
public analyze(tree: Parser.ProgramContext): IArrayIndexTypeError[] {
this.errors = [];
ParseTreeWalker.DEFAULT.walk(
new IndexTypeListener(this, this.context),
tree,
);
return this.errors;
}
/**
* Add an index type error
*/
public addError(
line: number,
column: number,
code: string,
actualType: string,
): void {
this.errors.push({
code,
line,
column,
actualType,
message: `Subscript index must be an unsigned integer type; got '${actualType}'`,
helpText:
"Use an unsigned integer type (u8, u16, u32, u64) for array and bit subscript indexes.",
});
}
/**
* Get all detected errors
*/
public getErrors(): IArrayIndexTypeError[] {
return this.errors;
}
}
export default ArrayIndexTypeAnalyzer;
|