c-next 0.2.17 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +7645 -5941
- package/dist/index.js.map +4 -4
- package/grammar/CNext.g4 +8 -0
- package/package.json +1 -3
- package/src/transpiler/Transpiler.ts +286 -26
- package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
- package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
- package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
- package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
- package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
- package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
- package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
- package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
- package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
- package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
- package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
- package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
- package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
- package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
- package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
- package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
- package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
- package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
- package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
- package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
- package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
- package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
- package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
- package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
- package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
- package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
- package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
- package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
- package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
- package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
- package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
- package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
- package/src/transpiler/state/CodeGenState.ts +20 -16
- package/src/transpiler/types/ICachedFileEntry.ts +7 -0
- package/src/utils/cache/CacheManager.ts +13 -2
- package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
- package/src/utils/constants/TypeConstants.ts +13 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mixed Type Category Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Detects binary operators whose two operands have different essential type
|
|
5
|
+
* categories (signed vs unsigned) at compile time.
|
|
6
|
+
*
|
|
7
|
+
* MISRA C:2012 Rule 10.4: "Both operands of an operator in which the usual
|
|
8
|
+
* arithmetic conversions are performed shall have the same essential type
|
|
9
|
+
* category." Combining a signed and an unsigned value (e.g. `u32 + i32`) relies
|
|
10
|
+
* on C's usual arithmetic conversions, whose result can be surprising (ADR-024).
|
|
11
|
+
*
|
|
12
|
+
* To combine values of different categories, the developer reinterprets one
|
|
13
|
+
* operand's bits to the other's category with bit indexing (ADR-007), e.g.
|
|
14
|
+
* `a + b[0, 32]`, making the conversion explicit.
|
|
15
|
+
*
|
|
16
|
+
* Integer literals are exempt: a bare literal has no fixed essential category —
|
|
17
|
+
* it is contextually typed to the other operand (ADR-052), so `a + 5` is fine.
|
|
18
|
+
* The rule fires only when BOTH operands resolve to concrete, fixed-width
|
|
19
|
+
* integer types of different category.
|
|
20
|
+
*
|
|
21
|
+
* Two-pass analysis:
|
|
22
|
+
* 1. Collect declarations into per-scope frames (function, named scope, block,
|
|
23
|
+
* and for-loop header), so a name is resolved against ITS scope — a same-named
|
|
24
|
+
* variable of a different category in another function OR a nested block never
|
|
25
|
+
* poisons the lookup (Issue #1085 review).
|
|
26
|
+
* 2. Walk each binary-operator level and compare adjacent operand categories,
|
|
27
|
+
* resolving each operand within its enclosing scope frame.
|
|
28
|
+
*
|
|
29
|
+
* Note: shift operators (<< / >>) are intentionally NOT checked here — MISRA
|
|
30
|
+
* Rule 10.4 only governs operators subject to the usual arithmetic conversions,
|
|
31
|
+
* and a shift count is promoted independently. A signed shift count is a Rule
|
|
32
|
+
* 10.1 concern handled elsewhere (Issue #1085 review).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { ParseTreeWalker, ParserRuleContext } from "antlr4ng";
|
|
36
|
+
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
37
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
38
|
+
import IMixedTypeCategoryError from "./types/IMixedTypeCategoryError";
|
|
39
|
+
import ParserUtils from "../../../utils/ParserUtils";
|
|
40
|
+
import TypeConstants from "../../../utils/constants/TypeConstants";
|
|
41
|
+
|
|
42
|
+
/** Essential type category of an operand, or null when it cannot be resolved. */
|
|
43
|
+
type Category = "signed" | "unsigned" | null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Declarations directly in one lexical scope (a function, named scope, block, or
|
|
47
|
+
* for-loop header), with a link to the enclosing scope. Resolution searches
|
|
48
|
+
* outward to the global frame, so inner declarations shadow outer ones.
|
|
49
|
+
*/
|
|
50
|
+
interface ScopeFrame {
|
|
51
|
+
readonly vars: Map<string, string>;
|
|
52
|
+
readonly parent: ScopeFrame | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* First pass: build per-scope frames. Frames are anchored to the function /
|
|
57
|
+
* scope context node so the second pass can find an operand's frame by walking
|
|
58
|
+
* up its parent chain — no shared walk state between the passes.
|
|
59
|
+
*/
|
|
60
|
+
class ScopeCollector extends CNextListener {
|
|
61
|
+
private readonly globalFrame: ScopeFrame = { vars: new Map(), parent: null };
|
|
62
|
+
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
64
|
+
private readonly frameOf: Map<ParserRuleContext, ScopeFrame> = new Map();
|
|
65
|
+
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
67
|
+
private readonly stack: ScopeFrame[] = [this.globalFrame];
|
|
68
|
+
|
|
69
|
+
public getGlobalFrame(): ScopeFrame {
|
|
70
|
+
return this.globalFrame;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public getFrameOf(): Map<ParserRuleContext, ScopeFrame> {
|
|
74
|
+
return this.frameOf;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private top(): ScopeFrame {
|
|
78
|
+
return this.stack.at(-1) ?? this.globalFrame;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private pushFrame(node: ParserRuleContext): void {
|
|
82
|
+
const frame: ScopeFrame = { vars: new Map(), parent: this.top() };
|
|
83
|
+
this.frameOf.set(node, frame);
|
|
84
|
+
this.stack.push(frame);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private popFrame(): void {
|
|
88
|
+
this.stack.pop();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private record(
|
|
92
|
+
typeCtx: Parser.TypeContext | null,
|
|
93
|
+
identifier: { getText(): string } | null,
|
|
94
|
+
): void {
|
|
95
|
+
if (!typeCtx || !identifier) return;
|
|
96
|
+
this.top().vars.set(identifier.getText(), typeCtx.getText());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override enterFunctionDeclaration = (
|
|
100
|
+
ctx: Parser.FunctionDeclarationContext,
|
|
101
|
+
): void => {
|
|
102
|
+
this.pushFrame(ctx);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
override exitFunctionDeclaration = (): void => {
|
|
106
|
+
this.popFrame();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
override enterScopeDeclaration = (
|
|
110
|
+
ctx: Parser.ScopeDeclarationContext,
|
|
111
|
+
): void => {
|
|
112
|
+
this.pushFrame(ctx);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
override exitScopeDeclaration = (): void => {
|
|
116
|
+
this.popFrame();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
override enterVariableDeclaration = (
|
|
120
|
+
ctx: Parser.VariableDeclarationContext,
|
|
121
|
+
): void => {
|
|
122
|
+
this.record(ctx.type(), ctx.IDENTIFIER());
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
override enterParameter = (ctx: Parser.ParameterContext): void => {
|
|
126
|
+
this.record(ctx.type(), ctx.IDENTIFIER());
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
override enterForVarDecl = (ctx: Parser.ForVarDeclContext): void => {
|
|
130
|
+
this.record(ctx.type(), ctx.IDENTIFIER());
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Each braced block (if/while/for body, and a function/scope body) is its own
|
|
134
|
+
// lexical scope, so a different-category redeclaration shadows only within the
|
|
135
|
+
// block instead of poisoning the name function-wide (Issue #1085 review).
|
|
136
|
+
override enterBlock = (ctx: Parser.BlockContext): void => {
|
|
137
|
+
this.pushFrame(ctx);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
override exitBlock = (): void => {
|
|
141
|
+
this.popFrame();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// The for-loop header is its own scope so the loop variable is confined to the
|
|
145
|
+
// loop (header + body) and never overwrites an outer same-named variable.
|
|
146
|
+
override enterForStatement = (ctx: Parser.ForStatementContext): void => {
|
|
147
|
+
this.pushFrame(ctx);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
override exitForStatement = (): void => {
|
|
151
|
+
this.popFrame();
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Second pass: detect binary operators combining mixed essential categories.
|
|
157
|
+
*/
|
|
158
|
+
class MixedCategoryListener extends CNextListener {
|
|
159
|
+
private readonly analyzer: MixedTypeCategoryAnalyzer;
|
|
160
|
+
|
|
161
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
162
|
+
private readonly globalFrame: ScopeFrame;
|
|
163
|
+
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
165
|
+
private readonly frameOf: Map<ParserRuleContext, ScopeFrame>;
|
|
166
|
+
|
|
167
|
+
constructor(
|
|
168
|
+
analyzer: MixedTypeCategoryAnalyzer,
|
|
169
|
+
globalFrame: ScopeFrame,
|
|
170
|
+
frameOf: Map<ParserRuleContext, ScopeFrame>,
|
|
171
|
+
) {
|
|
172
|
+
super();
|
|
173
|
+
this.analyzer = analyzer;
|
|
174
|
+
this.globalFrame = globalFrame;
|
|
175
|
+
this.frameOf = frameOf;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The scope frame enclosing an operand: nearest function/scope ancestor. */
|
|
179
|
+
private frameFor(ctx: ParserRuleContext): ScopeFrame {
|
|
180
|
+
let node: ParserRuleContext | null = ctx;
|
|
181
|
+
while (node) {
|
|
182
|
+
const frame = this.frameOf.get(node);
|
|
183
|
+
if (frame) return frame;
|
|
184
|
+
node = node.parent;
|
|
185
|
+
}
|
|
186
|
+
return this.globalFrame;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Map a known variable name to its essential type category within a scope. */
|
|
190
|
+
private categoryOfName(name: string, frame: ScopeFrame): Category {
|
|
191
|
+
let current: ScopeFrame | null = frame;
|
|
192
|
+
while (current) {
|
|
193
|
+
const typeName = current.vars.get(name);
|
|
194
|
+
if (typeName) {
|
|
195
|
+
if (TypeConstants.SIGNED_TYPES.includes(typeName)) return "signed";
|
|
196
|
+
if (TypeConstants.UNSIGNED_INT_TYPES.includes(typeName)) {
|
|
197
|
+
return "unsigned";
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
current = current.parent;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Collect the essential category of every classifiable VALUE leaf under one
|
|
208
|
+
* binary-operator operand, descending through nested operator levels and
|
|
209
|
+
* parentheses but never into a postfix suffix (an array index, bit-range
|
|
210
|
+
* argument, or call argument is not a value operand of THIS operator).
|
|
211
|
+
*
|
|
212
|
+
* A unary expression is a grammar leaf of the operator levels:
|
|
213
|
+
* - prefix `-`/`~` preserve the operand's category, so descend through them;
|
|
214
|
+
* - prefix `!` (essentially-Boolean result) and `&` (address-of, ADR-006)
|
|
215
|
+
* carry no signed/unsigned category — contribute null, so a mix like
|
|
216
|
+
* `!a = !b` is not falsely rejected (Issue #1085 review);
|
|
217
|
+
* - a postfix WITH a suffix (member/call/indexing/bit-extraction) cannot be
|
|
218
|
+
* positively classified — contribute null, which exempts the sanctioned
|
|
219
|
+
* cross-category form `x[0, 32]`;
|
|
220
|
+
* - a parenthesized expression contributes ALL of its own leaves (not merely
|
|
221
|
+
* the leftmost), so a compound operand is judged by its whole content.
|
|
222
|
+
*/
|
|
223
|
+
private collectOperandCategories(
|
|
224
|
+
ctx: ParserRuleContext,
|
|
225
|
+
frame: ScopeFrame,
|
|
226
|
+
out: Category[],
|
|
227
|
+
): void {
|
|
228
|
+
if (ctx instanceof Parser.UnaryExpressionContext) {
|
|
229
|
+
const inner = ctx.unaryExpression();
|
|
230
|
+
if (inner) {
|
|
231
|
+
const op = ctx.getChild(0)?.getText();
|
|
232
|
+
if (op === "!" || op === "&") {
|
|
233
|
+
out.push(null);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.collectOperandCategories(inner, frame, out);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const postfix = ctx.postfixExpression();
|
|
241
|
+
if (!postfix || postfix.getChildCount() > 1) {
|
|
242
|
+
out.push(null);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const primary = postfix.primaryExpression();
|
|
247
|
+
const parenthesized = primary?.expression();
|
|
248
|
+
if (parenthesized) {
|
|
249
|
+
this.collectOperandCategories(parenthesized, frame, out);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const identifier = primary?.IDENTIFIER();
|
|
254
|
+
out.push(
|
|
255
|
+
identifier ? this.categoryOfName(identifier.getText(), frame) : null,
|
|
256
|
+
);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (let i = 0; i < ctx.getChildCount(); i += 1) {
|
|
261
|
+
const child = ctx.getChild(i);
|
|
262
|
+
if (child instanceof ParserRuleContext) {
|
|
263
|
+
this.collectOperandCategories(child, frame, out);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The essential category of one operand of a binary-operator level: the single
|
|
270
|
+
* category shared by all its classifiable value leaves, or null when it has
|
|
271
|
+
* none OR when its own leaves are themselves mixed.
|
|
272
|
+
*
|
|
273
|
+
* Returning null for an internally-mixed operand prevents a CASCADE of
|
|
274
|
+
* duplicate errors: `a * b + c` (with `i32 a`, `u32 b`, `u32 c`) is reported
|
|
275
|
+
* once — at the `a * b` level — instead of again at the `+ c` level, where the
|
|
276
|
+
* product's category is genuinely ambiguous rather than `a`'s leftmost
|
|
277
|
+
* (Issue #1085 review). An internally-mixed operand is always reported at its
|
|
278
|
+
* own level, so nothing is missed. Because a resolved (non-null) category
|
|
279
|
+
* means every classifiable leaf agrees, comparing two resolved-but-differing
|
|
280
|
+
* operands always reflects a real signed/unsigned combination — no false
|
|
281
|
+
* positive on uniform code.
|
|
282
|
+
*/
|
|
283
|
+
private operandCategory(ctx: ParserRuleContext, frame: ScopeFrame): Category {
|
|
284
|
+
const leaves: Category[] = [];
|
|
285
|
+
this.collectOperandCategories(ctx, frame, leaves);
|
|
286
|
+
|
|
287
|
+
let resolved: Category = null;
|
|
288
|
+
for (const leaf of leaves) {
|
|
289
|
+
if (leaf === null) continue;
|
|
290
|
+
if (resolved === null) {
|
|
291
|
+
resolved = leaf;
|
|
292
|
+
} else if (resolved !== leaf) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return resolved;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Compare adjacent operands at one binary-operator level and report any pair
|
|
301
|
+
* whose categories are both resolved and differ.
|
|
302
|
+
*/
|
|
303
|
+
private checkLevel(operands: ParserRuleContext[]): void {
|
|
304
|
+
if (operands.length < 2) return;
|
|
305
|
+
const frame = this.frameFor(operands[0]);
|
|
306
|
+
for (let i = 0; i < operands.length - 1; i += 1) {
|
|
307
|
+
const left = this.operandCategory(operands[i], frame);
|
|
308
|
+
const right = this.operandCategory(operands[i + 1], frame);
|
|
309
|
+
if (left && right && left !== right) {
|
|
310
|
+
const { line, column } = ParserUtils.getPosition(operands[i + 1]);
|
|
311
|
+
this.analyzer.addError(line, column);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
override enterMultiplicativeExpression = (
|
|
317
|
+
ctx: Parser.MultiplicativeExpressionContext,
|
|
318
|
+
): void => {
|
|
319
|
+
this.checkLevel(ctx.unaryExpression());
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
override enterAdditiveExpression = (
|
|
323
|
+
ctx: Parser.AdditiveExpressionContext,
|
|
324
|
+
): void => {
|
|
325
|
+
this.checkLevel(ctx.multiplicativeExpression());
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
override enterBitwiseAndExpression = (
|
|
329
|
+
ctx: Parser.BitwiseAndExpressionContext,
|
|
330
|
+
): void => {
|
|
331
|
+
this.checkLevel(ctx.shiftExpression());
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
override enterBitwiseXorExpression = (
|
|
335
|
+
ctx: Parser.BitwiseXorExpressionContext,
|
|
336
|
+
): void => {
|
|
337
|
+
this.checkLevel(ctx.bitwiseAndExpression());
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
override enterBitwiseOrExpression = (
|
|
341
|
+
ctx: Parser.BitwiseOrExpressionContext,
|
|
342
|
+
): void => {
|
|
343
|
+
this.checkLevel(ctx.bitwiseXorExpression());
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
override enterRelationalExpression = (
|
|
347
|
+
ctx: Parser.RelationalExpressionContext,
|
|
348
|
+
): void => {
|
|
349
|
+
this.checkLevel(ctx.bitwiseOrExpression());
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
override enterEqualityExpression = (
|
|
353
|
+
ctx: Parser.EqualityExpressionContext,
|
|
354
|
+
): void => {
|
|
355
|
+
this.checkLevel(ctx.relationalExpression());
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Analyzer that detects binary operations mixing essential type categories.
|
|
361
|
+
*/
|
|
362
|
+
class MixedTypeCategoryAnalyzer {
|
|
363
|
+
private errors: IMixedTypeCategoryError[] = [];
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Analyze the parse tree for mixed-category binary operations.
|
|
367
|
+
*/
|
|
368
|
+
public analyze(tree: Parser.ProgramContext): IMixedTypeCategoryError[] {
|
|
369
|
+
this.errors = [];
|
|
370
|
+
|
|
371
|
+
const collector = new ScopeCollector();
|
|
372
|
+
ParseTreeWalker.DEFAULT.walk(collector, tree);
|
|
373
|
+
|
|
374
|
+
const listener = new MixedCategoryListener(
|
|
375
|
+
this,
|
|
376
|
+
collector.getGlobalFrame(),
|
|
377
|
+
collector.getFrameOf(),
|
|
378
|
+
);
|
|
379
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
380
|
+
|
|
381
|
+
return this.errors;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Add a mixed-category error.
|
|
386
|
+
*/
|
|
387
|
+
public addError(line: number, column: number): void {
|
|
388
|
+
this.errors.push({
|
|
389
|
+
code: "E0810",
|
|
390
|
+
line,
|
|
391
|
+
column,
|
|
392
|
+
message:
|
|
393
|
+
"Binary operator combines operands of different essential type categories (signed and unsigned)",
|
|
394
|
+
helpText:
|
|
395
|
+
"MISRA C:2012 Rule 10.4: both operands must share an essential type category. " +
|
|
396
|
+
"Reinterpret one operand's bits to match the other with bit indexing, e.g. value[0, 32] (ADR-007/ADR-024).",
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Get all detected errors.
|
|
402
|
+
*/
|
|
403
|
+
public getErrors(): IMixedTypeCategoryError[] {
|
|
404
|
+
return this.errors;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export default MixedTypeCategoryAnalyzer;
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
* 1. It's a small primitive type (u8, i8, u16, i16, u32, i32, u64, i64, bool)
|
|
15
15
|
* 2. It's not modified (directly or transitively)
|
|
16
16
|
* 3. It's not an array, struct, string, or callback
|
|
17
|
-
*
|
|
17
|
+
*
|
|
18
|
+
* Issue #1100: Subscript access no longer forces pointer semantics on its
|
|
19
|
+
* own. A scalar parameter subscripted with a single index is bit-indexing
|
|
20
|
+
* (ADR-007), not array access, so it stays eligible for pass-by-value.
|
|
21
|
+
* Only genuine array parameters (`isArray`, from explicit `T[N]` syntax,
|
|
22
|
+
* ADR-006) are excluded — via the isArray check below.
|
|
18
23
|
*/
|
|
19
24
|
|
|
20
25
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
@@ -173,8 +178,6 @@ class PassByValueAnalyzer {
|
|
|
173
178
|
|
|
174
179
|
// Initialize modified set
|
|
175
180
|
CodeGenState.modifiedParameters.set(funcName, new Set());
|
|
176
|
-
// Issue #579: Initialize subscript access tracking
|
|
177
|
-
CodeGenState.subscriptAccessedParameters.set(funcName, new Set());
|
|
178
181
|
CodeGenState.functionCallGraph.set(funcName, []);
|
|
179
182
|
|
|
180
183
|
// Walk the function body to find modifications and calls
|
|
@@ -225,15 +228,9 @@ class PassByValueAnalyzer {
|
|
|
225
228
|
);
|
|
226
229
|
}
|
|
227
230
|
|
|
228
|
-
// 2. Walk all expressions in this statement for function calls
|
|
231
|
+
// 2. Walk all expressions in this statement for function calls
|
|
229
232
|
for (const expr of StatementExpressionCollector.collectAll(stmt)) {
|
|
230
233
|
PassByValueAnalyzer.walkExpressionForCalls(funcName, paramSet, expr);
|
|
231
|
-
// Issue #579: Also track subscript read access on parameters
|
|
232
|
-
PassByValueAnalyzer.walkExpressionForSubscriptAccess(
|
|
233
|
-
funcName,
|
|
234
|
-
paramSet,
|
|
235
|
-
expr,
|
|
236
|
-
);
|
|
237
234
|
}
|
|
238
235
|
|
|
239
236
|
// 3. Recurse into child statements and blocks
|
|
@@ -266,21 +263,11 @@ class PassByValueAnalyzer {
|
|
|
266
263
|
const assign = stmt.assignmentStatement()!;
|
|
267
264
|
const target = assign.assignmentTarget();
|
|
268
265
|
|
|
269
|
-
const { baseIdentifier
|
|
270
|
-
AssignmentTargetExtractor.extract(target);
|
|
271
|
-
|
|
272
|
-
// Issue #579: Track subscript access on parameters (for write path)
|
|
273
|
-
if (
|
|
274
|
-
hasSingleIndexSubscript &&
|
|
275
|
-
baseIdentifier &&
|
|
276
|
-
paramSet.has(baseIdentifier)
|
|
277
|
-
) {
|
|
278
|
-
CodeGenState.subscriptAccessedParameters
|
|
279
|
-
.get(funcName)!
|
|
280
|
-
.add(baseIdentifier);
|
|
281
|
-
}
|
|
266
|
+
const { baseIdentifier } = AssignmentTargetExtractor.extract(target);
|
|
282
267
|
|
|
283
|
-
// Track as modified parameter
|
|
268
|
+
// Track as modified parameter (covers both `x <- value` and subscripted
|
|
269
|
+
// writes like `x[i] <- value` / `x[4] <- true` — both change x's value,
|
|
270
|
+
// so x must pass by pointer for the caller to observe the change)
|
|
284
271
|
if (baseIdentifier && paramSet.has(baseIdentifier)) {
|
|
285
272
|
CodeGenState.modifiedParameters.get(funcName)!.add(baseIdentifier);
|
|
286
273
|
}
|
|
@@ -309,67 +296,9 @@ class PassByValueAnalyzer {
|
|
|
309
296
|
}
|
|
310
297
|
}
|
|
311
298
|
|
|
312
|
-
/**
|
|
313
|
-
* Issue #579: Walk an expression tree to find subscript access on parameters.
|
|
314
|
-
* This tracks read access like `buf[i]` where buf is a parameter.
|
|
315
|
-
* Parameters with subscript access must become pointers.
|
|
316
|
-
*/
|
|
317
|
-
private static walkExpressionForSubscriptAccess(
|
|
318
|
-
funcName: string,
|
|
319
|
-
paramSet: Set<string>,
|
|
320
|
-
expr: Parser.ExpressionContext,
|
|
321
|
-
): void {
|
|
322
|
-
const ternary = expr.ternaryExpression();
|
|
323
|
-
if (ternary) {
|
|
324
|
-
for (const orExpr of ternary.orExpression()) {
|
|
325
|
-
PassByValueAnalyzer.walkOrExpression(orExpr, (unaryExpr) => {
|
|
326
|
-
PassByValueAnalyzer.handleSubscriptAccess(
|
|
327
|
-
funcName,
|
|
328
|
-
paramSet,
|
|
329
|
-
unaryExpr,
|
|
330
|
-
);
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Issue #579: Handle subscript access on a unary expression.
|
|
338
|
-
* Only tracks single-index subscript access (which could be array access).
|
|
339
|
-
* Two-index subscript (e.g., value[start, width]) is always bit extraction,
|
|
340
|
-
* so it doesn't require the parameter to become a pointer.
|
|
341
|
-
*/
|
|
342
|
-
private static handleSubscriptAccess(
|
|
343
|
-
funcName: string,
|
|
344
|
-
paramSet: Set<string>,
|
|
345
|
-
unaryExpr: Parser.UnaryExpressionContext,
|
|
346
|
-
): void {
|
|
347
|
-
const postfixExpr = unaryExpr.postfixExpression();
|
|
348
|
-
if (!postfixExpr) return;
|
|
349
|
-
|
|
350
|
-
const primary = postfixExpr.primaryExpression();
|
|
351
|
-
const ops = postfixExpr.postfixOp();
|
|
352
|
-
|
|
353
|
-
// Check if primary is a parameter and there's subscript access
|
|
354
|
-
const primaryId = primary.IDENTIFIER()?.getText();
|
|
355
|
-
if (!primaryId || !paramSet.has(primaryId)) {
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Only track SINGLE-index subscript access (potential array access)
|
|
360
|
-
// Two-index subscript like value[0, 8] is bit extraction, not array access
|
|
361
|
-
const hasSingleIndexSubscript = ops.some(
|
|
362
|
-
(op) => op.expression().length === 1,
|
|
363
|
-
);
|
|
364
|
-
if (hasSingleIndexSubscript) {
|
|
365
|
-
CodeGenState.subscriptAccessedParameters.get(funcName)!.add(primaryId);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
299
|
/**
|
|
370
300
|
* Generic walker for orExpression trees.
|
|
371
301
|
* Walks through the expression hierarchy and calls the handler for each unaryExpression.
|
|
372
|
-
* Used by both function call tracking and subscript access tracking.
|
|
373
302
|
*/
|
|
374
303
|
private static walkOrExpression(
|
|
375
304
|
orExpr: Parser.OrExpressionContext,
|
|
@@ -671,24 +600,14 @@ class PassByValueAnalyzer {
|
|
|
671
600
|
|
|
672
601
|
// Check if eligible for pass-by-value:
|
|
673
602
|
// - Is a small primitive type
|
|
674
|
-
// - Not an array
|
|
675
|
-
// - Not modified
|
|
676
|
-
//
|
|
603
|
+
// - Not an array (array parameters always decay to pointers, ADR-006)
|
|
604
|
+
// - Not modified (a subscripted bit-write, e.g. `x[4] <- true`, counts
|
|
605
|
+
// as a modification and is already tracked in `modified` above)
|
|
677
606
|
const isSmallPrimitive = SMALL_PRIMITIVES.has(paramSig.baseType);
|
|
678
607
|
const isArray = paramSig.isArray ?? false;
|
|
679
608
|
const isModified = modified.has(paramName);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
CodeGenState.subscriptAccessedParameters
|
|
683
|
-
.get(funcName)
|
|
684
|
-
?.has(paramName) ?? false;
|
|
685
|
-
|
|
686
|
-
if (
|
|
687
|
-
isSmallPrimitive &&
|
|
688
|
-
!isArray &&
|
|
689
|
-
!isModified &&
|
|
690
|
-
!hasSubscriptAccess
|
|
691
|
-
) {
|
|
609
|
+
|
|
610
|
+
if (isSmallPrimitive && !isArray && !isModified) {
|
|
692
611
|
passByValue.add(paramName);
|
|
693
612
|
}
|
|
694
613
|
}
|