c-next 0.1.0
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 +726 -0
- package/bin/cnext.js +5 -0
- package/grammar/C.g4 +1112 -0
- package/grammar/CNext.g4 +817 -0
- package/grammar/CPP14Lexer.g4 +282 -0
- package/grammar/CPP14Parser.g4 +1072 -0
- package/package.json +85 -0
- package/src/analysis/DivisionByZeroAnalyzer.ts +378 -0
- package/src/analysis/FunctionCallAnalyzer.ts +526 -0
- package/src/analysis/InitializationAnalyzer.ts +725 -0
- package/src/analysis/NullCheckAnalyzer.ts +427 -0
- package/src/analysis/types/IDivisionByZeroError.ts +25 -0
- package/src/analysis/types/IFunctionCallError.ts +17 -0
- package/src/analysis/types/IInitializationError.ts +55 -0
- package/src/analysis/types/INullCheckError.ts +25 -0
- package/src/codegen/CodeGenerator.ts +7945 -0
- package/src/codegen/CommentExtractor.ts +240 -0
- package/src/codegen/CommentFormatter.ts +155 -0
- package/src/codegen/HeaderGenerator.ts +265 -0
- package/src/codegen/TypeResolver.ts +365 -0
- package/src/codegen/types/ECommentType.ts +10 -0
- package/src/codegen/types/IComment.ts +21 -0
- package/src/codegen/types/ICommentError.ts +15 -0
- package/src/codegen/types/TOverflowBehavior.ts +6 -0
- package/src/codegen/types/TParameterInfo.ts +13 -0
- package/src/codegen/types/TTypeConstants.ts +94 -0
- package/src/codegen/types/TTypeInfo.ts +22 -0
- package/src/index.ts +518 -0
- package/src/lib/IncludeDiscovery.ts +131 -0
- package/src/lib/InputExpansion.ts +121 -0
- package/src/lib/PlatformIODetector.ts +162 -0
- package/src/lib/transpiler.ts +439 -0
- package/src/lib/types/ITranspileResult.ts +80 -0
- package/src/parser/c/grammar/C.interp +338 -0
- package/src/parser/c/grammar/C.tokens +229 -0
- package/src/parser/c/grammar/CLexer.interp +415 -0
- package/src/parser/c/grammar/CLexer.tokens +229 -0
- package/src/parser/c/grammar/CLexer.ts +750 -0
- package/src/parser/c/grammar/CListener.ts +976 -0
- package/src/parser/c/grammar/CParser.ts +9663 -0
- package/src/parser/c/grammar/CVisitor.ts +626 -0
- package/src/parser/cpp/grammar/CPP14Lexer.interp +478 -0
- package/src/parser/cpp/grammar/CPP14Lexer.tokens +264 -0
- package/src/parser/cpp/grammar/CPP14Lexer.ts +848 -0
- package/src/parser/cpp/grammar/CPP14Parser.interp +492 -0
- package/src/parser/cpp/grammar/CPP14Parser.tokens +264 -0
- package/src/parser/cpp/grammar/CPP14Parser.ts +19961 -0
- package/src/parser/cpp/grammar/CPP14ParserListener.ts +2120 -0
- package/src/parser/cpp/grammar/CPP14ParserVisitor.ts +1354 -0
- package/src/parser/grammar/CNext.interp +340 -0
- package/src/parser/grammar/CNext.tokens +214 -0
- package/src/parser/grammar/CNextLexer.interp +374 -0
- package/src/parser/grammar/CNextLexer.tokens +214 -0
- package/src/parser/grammar/CNextLexer.ts +668 -0
- package/src/parser/grammar/CNextListener.ts +1020 -0
- package/src/parser/grammar/CNextParser.ts +9239 -0
- package/src/parser/grammar/CNextVisitor.ts +654 -0
- package/src/preprocessor/Preprocessor.ts +301 -0
- package/src/preprocessor/ToolchainDetector.ts +225 -0
- package/src/preprocessor/types/IPreprocessResult.ts +39 -0
- package/src/preprocessor/types/IToolchain.ts +27 -0
- package/src/project/FileDiscovery.ts +236 -0
- package/src/project/Project.ts +425 -0
- package/src/project/types/IProjectConfig.ts +64 -0
- package/src/symbols/CNextSymbolCollector.ts +326 -0
- package/src/symbols/CSymbolCollector.ts +457 -0
- package/src/symbols/CppSymbolCollector.ts +362 -0
- package/src/symbols/SymbolTable.ts +312 -0
- package/src/symbols/types/IConflict.ts +20 -0
- package/src/types/ESourceLanguage.ts +10 -0
- package/src/types/ESymbolKind.ts +20 -0
- package/src/types/ISymbol.ts +45 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialization Analyzer
|
|
3
|
+
* Detects use of uninitialized variables (Rust-style E0381 errors)
|
|
4
|
+
*
|
|
5
|
+
* Phases:
|
|
6
|
+
* 1. Linear code tracking
|
|
7
|
+
* 2. Control flow (if/else branches)
|
|
8
|
+
* 3. Loop analysis
|
|
9
|
+
* 4. Scope tracking
|
|
10
|
+
* 5. Function parameters (always initialized)
|
|
11
|
+
* 7. Per-field struct tracking
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { ParseTreeWalker } from "antlr4ng";
|
|
15
|
+
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
16
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
17
|
+
import {
|
|
18
|
+
IInitializationError,
|
|
19
|
+
IDeclarationInfo,
|
|
20
|
+
} from "./types/IInitializationError";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tracks the initialization state of a variable
|
|
24
|
+
*/
|
|
25
|
+
interface IVariableState {
|
|
26
|
+
/** Where the variable was declared */
|
|
27
|
+
declaration: IDeclarationInfo;
|
|
28
|
+
/** Whether the variable has been initialized */
|
|
29
|
+
initialized: boolean;
|
|
30
|
+
/** Type of the variable (for struct field tracking) */
|
|
31
|
+
typeName: string | null;
|
|
32
|
+
/** For structs: which fields have been initialized */
|
|
33
|
+
initializedFields: Set<string>;
|
|
34
|
+
/** Is this a struct type? */
|
|
35
|
+
isStruct: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Represents a scope (function body, block, etc.)
|
|
40
|
+
*/
|
|
41
|
+
interface IScope {
|
|
42
|
+
/** Variables declared in this scope */
|
|
43
|
+
variables: Map<string, IVariableState>;
|
|
44
|
+
/** Parent scope (for nested blocks) */
|
|
45
|
+
parent: IScope | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Listener that walks the parse tree and tracks initialization
|
|
50
|
+
*/
|
|
51
|
+
class InitializationListener extends CNextListener {
|
|
52
|
+
private analyzer: InitializationAnalyzer;
|
|
53
|
+
|
|
54
|
+
/** Stack of saved states before each if statement */
|
|
55
|
+
private savedStates: Map<string, IVariableState>[] = [];
|
|
56
|
+
|
|
57
|
+
/** Track when we're inside a function call's argument list */
|
|
58
|
+
private inFunctionCallArgs: number = 0;
|
|
59
|
+
|
|
60
|
+
/** Track nesting depth inside functions/methods (0 = global level) */
|
|
61
|
+
private functionDepth: number = 0;
|
|
62
|
+
|
|
63
|
+
constructor(analyzer: InitializationAnalyzer) {
|
|
64
|
+
super();
|
|
65
|
+
this.analyzer = analyzer;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ========================================================================
|
|
69
|
+
// Scope Entry/Exit
|
|
70
|
+
// ========================================================================
|
|
71
|
+
|
|
72
|
+
override enterFunctionDeclaration = (
|
|
73
|
+
ctx: Parser.FunctionDeclarationContext,
|
|
74
|
+
): void => {
|
|
75
|
+
this.functionDepth++;
|
|
76
|
+
this.analyzer.enterScope();
|
|
77
|
+
|
|
78
|
+
// Declare parameters as initialized
|
|
79
|
+
const paramList = ctx.parameterList();
|
|
80
|
+
if (paramList) {
|
|
81
|
+
for (const param of paramList.parameter()) {
|
|
82
|
+
const name = param.IDENTIFIER().getText();
|
|
83
|
+
const line = param.start?.line ?? 0;
|
|
84
|
+
const column = param.start?.column ?? 0;
|
|
85
|
+
|
|
86
|
+
// Get type name for struct tracking
|
|
87
|
+
const typeCtx = param.type();
|
|
88
|
+
let typeName: string | null = null;
|
|
89
|
+
if (typeCtx.userType()) {
|
|
90
|
+
typeName = typeCtx.userType()!.IDENTIFIER().getText();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.analyzer.declareParameter(name, line, column, typeName);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
override exitFunctionDeclaration = (
|
|
99
|
+
_ctx: Parser.FunctionDeclarationContext,
|
|
100
|
+
): void => {
|
|
101
|
+
this.analyzer.exitScope();
|
|
102
|
+
this.functionDepth--;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
override enterBlock = (_ctx: Parser.BlockContext): void => {
|
|
106
|
+
this.analyzer.enterScope();
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
override exitBlock = (_ctx: Parser.BlockContext): void => {
|
|
110
|
+
this.analyzer.exitScope();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// ========================================================================
|
|
114
|
+
// Variable Declarations
|
|
115
|
+
// ========================================================================
|
|
116
|
+
|
|
117
|
+
override enterVariableDeclaration = (
|
|
118
|
+
ctx: Parser.VariableDeclarationContext,
|
|
119
|
+
): void => {
|
|
120
|
+
// Skip global variables - they're already handled by createGlobalScope()
|
|
121
|
+
if (this.functionDepth === 0) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const name = ctx.IDENTIFIER().getText();
|
|
126
|
+
const line = ctx.start?.line ?? 0;
|
|
127
|
+
const column = ctx.start?.column ?? 0;
|
|
128
|
+
const hasInitializer = ctx.expression() !== null;
|
|
129
|
+
|
|
130
|
+
// Get type name for struct tracking
|
|
131
|
+
const typeCtx = ctx.type();
|
|
132
|
+
let typeName: string | null = null;
|
|
133
|
+
if (typeCtx.userType()) {
|
|
134
|
+
typeName = typeCtx.userType()!.IDENTIFIER().getText();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.analyzer.declareVariable(name, line, column, hasInitializer, typeName);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ========================================================================
|
|
141
|
+
// Assignments
|
|
142
|
+
// ========================================================================
|
|
143
|
+
|
|
144
|
+
override enterAssignmentStatement = (
|
|
145
|
+
ctx: Parser.AssignmentStatementContext,
|
|
146
|
+
): void => {
|
|
147
|
+
const targetCtx = ctx.assignmentTarget();
|
|
148
|
+
|
|
149
|
+
// Simple variable assignment: x <- value
|
|
150
|
+
if (
|
|
151
|
+
targetCtx.IDENTIFIER() &&
|
|
152
|
+
!targetCtx.memberAccess() &&
|
|
153
|
+
!targetCtx.arrayAccess()
|
|
154
|
+
) {
|
|
155
|
+
const name = targetCtx.IDENTIFIER()!.getText();
|
|
156
|
+
this.analyzer.recordAssignment(name);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Member access: p.x <- value (only first-level field)
|
|
160
|
+
if (targetCtx.memberAccess()) {
|
|
161
|
+
const memberCtx = targetCtx.memberAccess()!;
|
|
162
|
+
const identifiers = memberCtx.IDENTIFIER();
|
|
163
|
+
if (identifiers.length >= 2) {
|
|
164
|
+
const varName = identifiers[0].getText();
|
|
165
|
+
const fieldName = identifiers[1].getText();
|
|
166
|
+
this.analyzer.recordAssignment(varName, fieldName);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Array access: arr[i] <- value
|
|
171
|
+
if (targetCtx.arrayAccess()) {
|
|
172
|
+
const arrayName = targetCtx.arrayAccess()!.IDENTIFIER().getText();
|
|
173
|
+
// Array element assignment initializes that element, but for simplicity
|
|
174
|
+
// we'll consider the array as a whole. More granular tracking would be complex.
|
|
175
|
+
this.analyzer.recordAssignment(arrayName);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// ========================================================================
|
|
180
|
+
// Function Call Arguments (ADR-006: pass-by-reference may initialize)
|
|
181
|
+
// ========================================================================
|
|
182
|
+
|
|
183
|
+
override enterArgumentList = (_ctx: Parser.ArgumentListContext): void => {
|
|
184
|
+
// When inside function call arguments, variables passed may be output params
|
|
185
|
+
// We don't error on uninitialized reads, and we mark them as initialized after
|
|
186
|
+
this.inFunctionCallArgs++;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
override exitArgumentList = (ctx: Parser.ArgumentListContext): void => {
|
|
190
|
+
this.inFunctionCallArgs--;
|
|
191
|
+
|
|
192
|
+
// Mark any simple identifiers passed as arguments as initialized
|
|
193
|
+
// (they might be output parameters that the function writes to)
|
|
194
|
+
for (const expr of ctx.expression()) {
|
|
195
|
+
// Walk down to find simple identifiers
|
|
196
|
+
this.markArgumentsAsInitialized(expr);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Recursively find and mark simple identifier arguments as initialized
|
|
202
|
+
*/
|
|
203
|
+
private markArgumentsAsInitialized(expr: Parser.ExpressionContext): void {
|
|
204
|
+
// Navigate through expression layers to find primary expression
|
|
205
|
+
const ternary = expr.ternaryExpression();
|
|
206
|
+
if (!ternary) return;
|
|
207
|
+
const orExprs = ternary.orExpression();
|
|
208
|
+
if (orExprs.length === 0) return;
|
|
209
|
+
const or = orExprs[0];
|
|
210
|
+
if (!or) return;
|
|
211
|
+
const and = or.andExpression(0);
|
|
212
|
+
if (!and) return;
|
|
213
|
+
const eq = and.equalityExpression(0);
|
|
214
|
+
if (!eq) return;
|
|
215
|
+
const rel = eq.relationalExpression(0);
|
|
216
|
+
if (!rel) return;
|
|
217
|
+
const bor = rel.bitwiseOrExpression(0);
|
|
218
|
+
if (!bor) return;
|
|
219
|
+
const bxor = bor.bitwiseXorExpression(0);
|
|
220
|
+
if (!bxor) return;
|
|
221
|
+
const band = bxor.bitwiseAndExpression(0);
|
|
222
|
+
if (!band) return;
|
|
223
|
+
const shift = band.shiftExpression(0);
|
|
224
|
+
if (!shift) return;
|
|
225
|
+
const add = shift.additiveExpression(0);
|
|
226
|
+
if (!add) return;
|
|
227
|
+
const mult = add.multiplicativeExpression(0);
|
|
228
|
+
if (!mult) return;
|
|
229
|
+
const unary = mult.unaryExpression(0);
|
|
230
|
+
if (!unary) return;
|
|
231
|
+
const postfix = unary.postfixExpression();
|
|
232
|
+
if (!postfix) return;
|
|
233
|
+
const primary = postfix.primaryExpression();
|
|
234
|
+
if (!primary) return;
|
|
235
|
+
|
|
236
|
+
// Found primary expression - check if it's a simple identifier
|
|
237
|
+
if (primary.IDENTIFIER()) {
|
|
238
|
+
const name = primary.IDENTIFIER()!.getText();
|
|
239
|
+
this.analyzer.recordAssignment(name);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ========================================================================
|
|
244
|
+
// Variable Reads (in expressions)
|
|
245
|
+
// ========================================================================
|
|
246
|
+
|
|
247
|
+
override enterPrimaryExpression = (
|
|
248
|
+
ctx: Parser.PrimaryExpressionContext,
|
|
249
|
+
): void => {
|
|
250
|
+
// Skip if we're in a write context (left side of assignment)
|
|
251
|
+
if (this.analyzer.isInWriteContext()) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Skip if we're in function call arguments (might be output param)
|
|
256
|
+
if (this.inFunctionCallArgs > 0) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check for simple identifier
|
|
261
|
+
if (ctx.IDENTIFIER()) {
|
|
262
|
+
const name = ctx.IDENTIFIER()!.getText();
|
|
263
|
+
const line = ctx.start?.line ?? 0;
|
|
264
|
+
const column = ctx.start?.column ?? 0;
|
|
265
|
+
|
|
266
|
+
this.analyzer.checkRead(name, line, column);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
override enterMemberAccess = (ctx: Parser.MemberAccessContext): void => {
|
|
271
|
+
// Skip if we're in a write context
|
|
272
|
+
if (this.analyzer.isInWriteContext()) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Skip if we're in function call arguments
|
|
277
|
+
if (this.inFunctionCallArgs > 0) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const identifiers = ctx.IDENTIFIER();
|
|
282
|
+
if (identifiers.length >= 2) {
|
|
283
|
+
const varName = identifiers[0].getText();
|
|
284
|
+
const fieldName = identifiers[1].getText();
|
|
285
|
+
const line = ctx.start?.line ?? 0;
|
|
286
|
+
const column = ctx.start?.column ?? 0;
|
|
287
|
+
|
|
288
|
+
// Check if the specific field is initialized
|
|
289
|
+
this.analyzer.checkRead(varName, line, column, fieldName);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// ========================================================================
|
|
294
|
+
// Control Flow: If Statements
|
|
295
|
+
// ========================================================================
|
|
296
|
+
|
|
297
|
+
override enterIfStatement = (_ctx: Parser.IfStatementContext): void => {
|
|
298
|
+
// Save current state before entering if
|
|
299
|
+
const stateBefore = this.analyzer.cloneScopeState();
|
|
300
|
+
this.savedStates.push(stateBefore);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
override exitIfStatement = (ctx: Parser.IfStatementContext): void => {
|
|
304
|
+
const stateBefore = this.savedStates.pop();
|
|
305
|
+
if (!stateBefore) return;
|
|
306
|
+
|
|
307
|
+
// Check if there's an else clause
|
|
308
|
+
const hasElse = ctx.ELSE() !== null;
|
|
309
|
+
|
|
310
|
+
if (hasElse) {
|
|
311
|
+
// With else: the tree walker processes both branches in order.
|
|
312
|
+
// If a variable is initialized in both branches, it ends up initialized.
|
|
313
|
+
// If only one branch initializes it, it may or may not be initialized
|
|
314
|
+
// depending on which branch ran last in the traversal.
|
|
315
|
+
// For now, we'll trust the final state - this is optimistic but
|
|
316
|
+
// often correct for the common pattern where both branches initialize.
|
|
317
|
+
// (A more precise analysis would track both branches separately)
|
|
318
|
+
// Don't restore - keep whatever state the branches produced.
|
|
319
|
+
} else {
|
|
320
|
+
// No else: the if might not execute, so restore to state before if
|
|
321
|
+
// Any initializations inside the if are not guaranteed
|
|
322
|
+
this.analyzer.restoreFromState(stateBefore);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// ========================================================================
|
|
327
|
+
// Control Flow: Loops
|
|
328
|
+
// ========================================================================
|
|
329
|
+
|
|
330
|
+
override enterWhileStatement = (_ctx: Parser.WhileStatementContext): void => {
|
|
331
|
+
// Save state before loop - we'll restore after because loop might not run
|
|
332
|
+
this.savedStates.push(this.analyzer.cloneScopeState());
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
override exitWhileStatement = (_ctx: Parser.WhileStatementContext): void => {
|
|
336
|
+
// Loops are conservative: we assume they might not run at all
|
|
337
|
+
// So we restore state to before the loop
|
|
338
|
+
const stateBeforeLoop = this.savedStates.pop();
|
|
339
|
+
if (stateBeforeLoop) {
|
|
340
|
+
this.analyzer.restoreFromState(stateBeforeLoop);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
override enterForStatement = (_ctx: Parser.ForStatementContext): void => {
|
|
345
|
+
// Save state before loop
|
|
346
|
+
this.savedStates.push(this.analyzer.cloneScopeState());
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
override exitForStatement = (_ctx: Parser.ForStatementContext): void => {
|
|
350
|
+
// Same as while - conservative approach
|
|
351
|
+
const stateBeforeLoop = this.savedStates.pop();
|
|
352
|
+
if (stateBeforeLoop) {
|
|
353
|
+
this.analyzer.restoreFromState(stateBeforeLoop);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Analyzes C-Next AST for use-before-initialization errors
|
|
360
|
+
*/
|
|
361
|
+
export class InitializationAnalyzer {
|
|
362
|
+
private errors: IInitializationError[] = [];
|
|
363
|
+
|
|
364
|
+
private currentScope: IScope | null = null;
|
|
365
|
+
|
|
366
|
+
/** Known struct types and their fields */
|
|
367
|
+
private structFields: Map<string, Set<string>> = new Map();
|
|
368
|
+
|
|
369
|
+
/** Track if we're processing a write target (left side of assignment) */
|
|
370
|
+
private inWriteContext: boolean = false;
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Analyze a parsed program for initialization errors
|
|
374
|
+
* @param tree The parsed program AST
|
|
375
|
+
* @returns Array of initialization errors
|
|
376
|
+
*/
|
|
377
|
+
public analyze(tree: Parser.ProgramContext): IInitializationError[] {
|
|
378
|
+
this.errors = [];
|
|
379
|
+
this.currentScope = null;
|
|
380
|
+
this.structFields.clear();
|
|
381
|
+
|
|
382
|
+
// First pass: collect struct definitions
|
|
383
|
+
this.collectStructDefinitions(tree);
|
|
384
|
+
|
|
385
|
+
// Create global scope with all global/namespace variable declarations
|
|
386
|
+
this.createGlobalScope(tree);
|
|
387
|
+
|
|
388
|
+
// Second pass: analyze initialization
|
|
389
|
+
const listener = new InitializationListener(this);
|
|
390
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
391
|
+
|
|
392
|
+
return this.errors;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Create the global scope with all top-level variable declarations
|
|
397
|
+
* Global variables are considered "initialized" (zero-init by ADR-015)
|
|
398
|
+
*/
|
|
399
|
+
private createGlobalScope(tree: Parser.ProgramContext): void {
|
|
400
|
+
this.enterScope(); // Create global scope
|
|
401
|
+
|
|
402
|
+
for (const decl of tree.declaration()) {
|
|
403
|
+
// Global variable declarations
|
|
404
|
+
const varDecl = decl.variableDeclaration();
|
|
405
|
+
if (varDecl) {
|
|
406
|
+
const name = varDecl.IDENTIFIER().getText();
|
|
407
|
+
const line = varDecl.start?.line ?? 0;
|
|
408
|
+
const column = varDecl.start?.column ?? 0;
|
|
409
|
+
|
|
410
|
+
// Get type for struct tracking
|
|
411
|
+
const typeCtx = varDecl.type();
|
|
412
|
+
let typeName: string | null = null;
|
|
413
|
+
if (typeCtx.userType()) {
|
|
414
|
+
typeName = typeCtx.userType()!.IDENTIFIER().getText();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Globals are always initialized (zero-init or explicit)
|
|
418
|
+
this.declareVariable(name, line, column, true, typeName);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Scope member variables (ADR-016: renamed from namespace)
|
|
422
|
+
const scopeDecl = decl.scopeDeclaration();
|
|
423
|
+
if (scopeDecl) {
|
|
424
|
+
const scopeName = scopeDecl.IDENTIFIER().getText();
|
|
425
|
+
for (const member of scopeDecl.scopeMember()) {
|
|
426
|
+
const memberVar = member.variableDeclaration();
|
|
427
|
+
if (memberVar) {
|
|
428
|
+
const varName = memberVar.IDENTIFIER().getText();
|
|
429
|
+
const fullName = `${scopeName}_${varName}`; // Mangled name
|
|
430
|
+
const line = memberVar.start?.line ?? 0;
|
|
431
|
+
const column = memberVar.start?.column ?? 0;
|
|
432
|
+
|
|
433
|
+
const typeCtx = memberVar.type();
|
|
434
|
+
let typeName: string | null = null;
|
|
435
|
+
if (typeCtx.userType()) {
|
|
436
|
+
typeName = typeCtx.userType()!.IDENTIFIER().getText();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Also register with raw name for scope resolution
|
|
440
|
+
this.declareVariable(varName, line, column, true, typeName);
|
|
441
|
+
this.declareVariable(fullName, line, column, true, typeName);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Collect struct definitions to know their fields
|
|
450
|
+
*/
|
|
451
|
+
private collectStructDefinitions(tree: Parser.ProgramContext): void {
|
|
452
|
+
for (const decl of tree.declaration()) {
|
|
453
|
+
const structDecl = decl.structDeclaration();
|
|
454
|
+
if (structDecl) {
|
|
455
|
+
const structName = structDecl.IDENTIFIER().getText();
|
|
456
|
+
const fields = new Set<string>();
|
|
457
|
+
|
|
458
|
+
for (const member of structDecl.structMember()) {
|
|
459
|
+
const fieldName = member.IDENTIFIER().getText();
|
|
460
|
+
fields.add(fieldName);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
this.structFields.set(structName, fields);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ========================================================================
|
|
469
|
+
// Scope Management
|
|
470
|
+
// ========================================================================
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Enter a new scope (function, block, etc.)
|
|
474
|
+
*/
|
|
475
|
+
public enterScope(): void {
|
|
476
|
+
const newScope: IScope = {
|
|
477
|
+
variables: new Map(),
|
|
478
|
+
parent: this.currentScope,
|
|
479
|
+
};
|
|
480
|
+
this.currentScope = newScope;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Exit the current scope
|
|
485
|
+
*/
|
|
486
|
+
public exitScope(): void {
|
|
487
|
+
if (this.currentScope) {
|
|
488
|
+
this.currentScope = this.currentScope.parent;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ========================================================================
|
|
493
|
+
// Variable Tracking
|
|
494
|
+
// ========================================================================
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Declare a variable (may or may not be initialized)
|
|
498
|
+
*/
|
|
499
|
+
public declareVariable(
|
|
500
|
+
name: string,
|
|
501
|
+
line: number,
|
|
502
|
+
column: number,
|
|
503
|
+
hasInitializer: boolean,
|
|
504
|
+
typeName: string | null,
|
|
505
|
+
): void {
|
|
506
|
+
if (!this.currentScope) {
|
|
507
|
+
// Global scope - create implicit scope
|
|
508
|
+
this.enterScope();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const isStruct = typeName !== null && this.structFields.has(typeName);
|
|
512
|
+
const fields = isStruct
|
|
513
|
+
? this.structFields.get(typeName)!
|
|
514
|
+
: new Set<string>();
|
|
515
|
+
|
|
516
|
+
const state: IVariableState = {
|
|
517
|
+
declaration: { name, line, column },
|
|
518
|
+
initialized: hasInitializer,
|
|
519
|
+
typeName,
|
|
520
|
+
isStruct,
|
|
521
|
+
// If initialized with full struct initializer, all fields are initialized
|
|
522
|
+
initializedFields: hasInitializer ? new Set(fields) : new Set(),
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
this.currentScope!.variables.set(name, state);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Record that a variable (or field) has been assigned
|
|
530
|
+
*/
|
|
531
|
+
public recordAssignment(name: string, field?: string): void {
|
|
532
|
+
const state = this.lookupVariable(name);
|
|
533
|
+
if (state) {
|
|
534
|
+
if (field) {
|
|
535
|
+
// Field assignment
|
|
536
|
+
state.initializedFields.add(field);
|
|
537
|
+
// Check if all fields are now initialized
|
|
538
|
+
if (state.isStruct && state.typeName) {
|
|
539
|
+
const allFields = this.structFields.get(state.typeName);
|
|
540
|
+
if (allFields) {
|
|
541
|
+
const allInitialized = [...allFields].every((f) =>
|
|
542
|
+
state.initializedFields.has(f),
|
|
543
|
+
);
|
|
544
|
+
if (allInitialized) {
|
|
545
|
+
state.initialized = true;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
// Whole variable assignment
|
|
551
|
+
state.initialized = true;
|
|
552
|
+
// Mark all fields as initialized too
|
|
553
|
+
if (state.isStruct && state.typeName) {
|
|
554
|
+
const fields = this.structFields.get(state.typeName);
|
|
555
|
+
if (fields) {
|
|
556
|
+
state.initializedFields = new Set(fields);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Check if a variable (or field) is used before initialization
|
|
565
|
+
*/
|
|
566
|
+
public checkRead(
|
|
567
|
+
name: string,
|
|
568
|
+
line: number,
|
|
569
|
+
column: number,
|
|
570
|
+
field?: string,
|
|
571
|
+
): void {
|
|
572
|
+
const state = this.lookupVariable(name);
|
|
573
|
+
|
|
574
|
+
if (!state) {
|
|
575
|
+
// Variable not found in any scope - this would be a different error
|
|
576
|
+
// (undefined variable), not an initialization error
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (field) {
|
|
581
|
+
// Reading a specific field
|
|
582
|
+
if (!state.initializedFields.has(field)) {
|
|
583
|
+
this.addError(
|
|
584
|
+
`${name}.${field}`,
|
|
585
|
+
line,
|
|
586
|
+
column,
|
|
587
|
+
state.declaration,
|
|
588
|
+
false, // Definitely uninitialized
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
} else if (!state.initialized) {
|
|
592
|
+
// Reading the whole variable
|
|
593
|
+
this.addError(
|
|
594
|
+
name,
|
|
595
|
+
line,
|
|
596
|
+
column,
|
|
597
|
+
state.declaration,
|
|
598
|
+
false, // Definitely uninitialized
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Look up a variable in the current scope chain
|
|
605
|
+
*/
|
|
606
|
+
private lookupVariable(name: string): IVariableState | null {
|
|
607
|
+
let scope = this.currentScope;
|
|
608
|
+
while (scope) {
|
|
609
|
+
if (scope.variables.has(name)) {
|
|
610
|
+
return scope.variables.get(name)!;
|
|
611
|
+
}
|
|
612
|
+
scope = scope.parent;
|
|
613
|
+
}
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ========================================================================
|
|
618
|
+
// Control Flow
|
|
619
|
+
// ========================================================================
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Clone the current scope state for branch analysis
|
|
623
|
+
*/
|
|
624
|
+
public cloneScopeState(): Map<string, IVariableState> {
|
|
625
|
+
const clone = new Map<string, IVariableState>();
|
|
626
|
+
let scope = this.currentScope;
|
|
627
|
+
while (scope) {
|
|
628
|
+
for (const [name, state] of scope.variables) {
|
|
629
|
+
if (!clone.has(name)) {
|
|
630
|
+
clone.set(name, {
|
|
631
|
+
...state,
|
|
632
|
+
initializedFields: new Set(state.initializedFields),
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
scope = scope.parent;
|
|
637
|
+
}
|
|
638
|
+
return clone;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Restore initialization state from a saved snapshot
|
|
643
|
+
* Used for control flow analysis to "undo" branch changes
|
|
644
|
+
*/
|
|
645
|
+
public restoreFromState(savedState: Map<string, IVariableState>): void {
|
|
646
|
+
for (const [name, savedVarState] of savedState) {
|
|
647
|
+
const currentVar = this.lookupVariable(name);
|
|
648
|
+
if (currentVar) {
|
|
649
|
+
// Restore the initialization state from the saved snapshot
|
|
650
|
+
currentVar.initialized = savedVarState.initialized;
|
|
651
|
+
currentVar.initializedFields = new Set(savedVarState.initializedFields);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// ========================================================================
|
|
657
|
+
// Error Reporting
|
|
658
|
+
// ========================================================================
|
|
659
|
+
|
|
660
|
+
private addError(
|
|
661
|
+
variable: string,
|
|
662
|
+
line: number,
|
|
663
|
+
column: number,
|
|
664
|
+
declaration: IDeclarationInfo,
|
|
665
|
+
mayBeUninitialized: boolean,
|
|
666
|
+
): void {
|
|
667
|
+
const certainty = mayBeUninitialized ? "possibly " : "";
|
|
668
|
+
this.errors.push({
|
|
669
|
+
code: "E0381",
|
|
670
|
+
variable,
|
|
671
|
+
line,
|
|
672
|
+
column,
|
|
673
|
+
declaration,
|
|
674
|
+
mayBeUninitialized,
|
|
675
|
+
message: `use of ${certainty}uninitialized variable '${variable}'`,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// ========================================================================
|
|
680
|
+
// Write Context Tracking
|
|
681
|
+
// ========================================================================
|
|
682
|
+
|
|
683
|
+
public setWriteContext(inWrite: boolean): void {
|
|
684
|
+
this.inWriteContext = inWrite;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
public isInWriteContext(): boolean {
|
|
688
|
+
return this.inWriteContext;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ========================================================================
|
|
692
|
+
// Function Parameters
|
|
693
|
+
// ========================================================================
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Declare function parameters (always initialized by caller)
|
|
697
|
+
*/
|
|
698
|
+
public declareParameter(
|
|
699
|
+
name: string,
|
|
700
|
+
line: number,
|
|
701
|
+
column: number,
|
|
702
|
+
typeName: string | null,
|
|
703
|
+
): void {
|
|
704
|
+
if (!this.currentScope) {
|
|
705
|
+
this.enterScope();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const isStruct = typeName !== null && this.structFields.has(typeName);
|
|
709
|
+
const fields = isStruct
|
|
710
|
+
? this.structFields.get(typeName)!
|
|
711
|
+
: new Set<string>();
|
|
712
|
+
|
|
713
|
+
const state: IVariableState = {
|
|
714
|
+
declaration: { name, line, column },
|
|
715
|
+
initialized: true, // Parameters are always initialized by caller
|
|
716
|
+
typeName,
|
|
717
|
+
isStruct,
|
|
718
|
+
initializedFields: new Set(fields), // All fields initialized
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
this.currentScope!.variables.set(name, state);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export default InitializationAnalyzer;
|