c-next 0.2.16 → 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 +18 -2
- package/dist/index.js +8897 -6260
- package/dist/index.js.map +4 -4
- package/grammar/CNext.g4 +12 -0
- package/package.json +4 -2
- package/src/transpiler/Transpiler.ts +376 -48
- package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
- 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 +65 -10
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
- 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/SignedShiftAnalyzer.ts +124 -12
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
- package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
- 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/__tests__/SignedShiftAnalyzer.test.ts +211 -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 +1318 -1178
- 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 +62 -2
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +55 -4
- package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
- package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
- 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 +196 -98
- 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.coverage.test.ts +165 -17
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +163 -35
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
- 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 +97 -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/declarationGenerators/ScopeGenerator.ts +26 -7
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
- package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +76 -58
- package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
- package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
- package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
- 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/ParameterInputAdapter.ts +17 -3
- package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +240 -42
- package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
- package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +322 -9
- package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
- package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
- 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/codegen/types/ICallbackTypeInfo.ts +2 -1
- package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
- package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +140 -24
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
- package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
- package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
- package/src/transpiler/state/CodeGenState.ts +91 -22
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
- package/src/transpiler/types/ICachedFileEntry.ts +7 -0
- package/src/utils/LiteralUtils.ts +23 -0
- package/src/utils/__tests__/LiteralUtils.test.ts +101 -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
- package/src/utils/types/IParameterSymbol.ts +1 -0
|
@@ -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
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return-Path Analyzer
|
|
3
|
+
* ADR-112 / Issue #1040: reject a non-void function that can reach the end of
|
|
4
|
+
* its body without returning a value (undefined behavior in C).
|
|
5
|
+
*
|
|
6
|
+
* The analysis is intentionally strict and conservative (sound): it never
|
|
7
|
+
* accepts a function that might fall through, and it does not attempt to prove
|
|
8
|
+
* that loops are infinite or that a switch over an enum is exhaustive. Where it
|
|
9
|
+
* cannot prove a return, an explicit `return <value>` is required.
|
|
10
|
+
*
|
|
11
|
+
* C-Next has no break/continue (ADR-026), so loop bodies have no early
|
|
12
|
+
* structural exits to reason about.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { ParseTreeWalker } from "antlr4ng";
|
|
16
|
+
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
17
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
18
|
+
import IReturnPathError from "./types/IReturnPathError";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Does executing this statement guarantee that the enclosing function returns a
|
|
22
|
+
* value before control passes beyond it?
|
|
23
|
+
*/
|
|
24
|
+
function statementDefinitelyReturns(ctx: Parser.StatementContext): boolean {
|
|
25
|
+
const returnStmt = ctx.returnStatement();
|
|
26
|
+
if (returnStmt) {
|
|
27
|
+
// A bare `return;` (no expression) returns no value, so it does not satisfy
|
|
28
|
+
// a non-void function.
|
|
29
|
+
return returnStmt.expression() !== null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ifStmt = ctx.ifStatement();
|
|
33
|
+
if (ifStmt) {
|
|
34
|
+
return ifDefinitelyReturns(ifStmt);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const switchStmt = ctx.switchStatement();
|
|
38
|
+
if (switchStmt) {
|
|
39
|
+
return switchDefinitelyReturns(switchStmt);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const doWhileStmt = ctx.doWhileStatement();
|
|
43
|
+
if (doWhileStmt) {
|
|
44
|
+
// The body always executes at least once.
|
|
45
|
+
return blockDefinitelyReturns(doWhileStmt.block());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (ctx.foreverStatement()) {
|
|
49
|
+
// ADR-113: a `forever` loop is divergent — C-Next has no break/continue
|
|
50
|
+
// (ADR-026), so control never passes beyond it. It is therefore a terminal
|
|
51
|
+
// path, like an unconditional return, and the function never falls through
|
|
52
|
+
// here. This is the shared "divergence" primitive ADR-114 (#849) reuses.
|
|
53
|
+
//
|
|
54
|
+
// `forever` is void-only (E0705, enforced in codegen). Marking it terminal
|
|
55
|
+
// here keeps a non-void function containing a `forever` loop from emitting a
|
|
56
|
+
// misleading E0704 ("must return a value") instead of the precise E0705.
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const block = ctx.block();
|
|
61
|
+
if (block) {
|
|
62
|
+
return blockDefinitelyReturns(block);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const criticalStmt = ctx.criticalStatement();
|
|
66
|
+
if (criticalStmt) {
|
|
67
|
+
return blockDefinitelyReturns(criticalStmt.block());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// while / for (the body may not execute), variable declarations, assignments,
|
|
71
|
+
// and expression statements never guarantee a return on their own.
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A block guarantees a return iff any of its statements does: statements after
|
|
77
|
+
* an unconditional return are unreachable.
|
|
78
|
+
*/
|
|
79
|
+
function blockDefinitelyReturns(ctx: Parser.BlockContext): boolean {
|
|
80
|
+
return ctx.statement().some(statementDefinitelyReturns);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* An if guarantees a return only when an `else` is present and both branches
|
|
85
|
+
* guarantee a return. `else if` chains recurse through the else branch.
|
|
86
|
+
*/
|
|
87
|
+
function ifDefinitelyReturns(ctx: Parser.IfStatementContext): boolean {
|
|
88
|
+
const branches = ctx.statement();
|
|
89
|
+
if (branches.length < 2) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return (
|
|
93
|
+
statementDefinitelyReturns(branches[0]) &&
|
|
94
|
+
statementDefinitelyReturns(branches[1])
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A switch guarantees a return only when a `default` is present and every case
|
|
100
|
+
* block and the default block guarantee a return.
|
|
101
|
+
*/
|
|
102
|
+
function switchDefinitelyReturns(ctx: Parser.SwitchStatementContext): boolean {
|
|
103
|
+
const defaultCase = ctx.defaultCase();
|
|
104
|
+
if (!defaultCase) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const everyCaseReturns = ctx
|
|
108
|
+
.switchCase()
|
|
109
|
+
.every((switchCase) => blockDefinitelyReturns(switchCase.block()));
|
|
110
|
+
return everyCaseReturns && blockDefinitelyReturns(defaultCase.block());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Listener that flags non-void functions whose body can fall through.
|
|
115
|
+
*/
|
|
116
|
+
class ReturnPathListener extends CNextListener {
|
|
117
|
+
private readonly analyzer: ReturnPathAnalyzer;
|
|
118
|
+
|
|
119
|
+
constructor(analyzer: ReturnPathAnalyzer) {
|
|
120
|
+
super();
|
|
121
|
+
this.analyzer = analyzer;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override enterFunctionDeclaration = (
|
|
125
|
+
ctx: Parser.FunctionDeclarationContext,
|
|
126
|
+
): void => {
|
|
127
|
+
// Void functions are allowed to fall off the end.
|
|
128
|
+
if (ctx.type().getText() === "void") {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (blockDefinitelyReturns(ctx.block())) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const identifier = ctx.IDENTIFIER();
|
|
137
|
+
this.analyzer.addError(
|
|
138
|
+
identifier.getText(),
|
|
139
|
+
identifier.symbol.line,
|
|
140
|
+
identifier.symbol.column,
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Analyzer for missing return paths (ADR-112).
|
|
147
|
+
*/
|
|
148
|
+
class ReturnPathAnalyzer {
|
|
149
|
+
private errors: IReturnPathError[] = [];
|
|
150
|
+
|
|
151
|
+
public analyze(tree: Parser.ProgramContext): IReturnPathError[] {
|
|
152
|
+
this.errors = [];
|
|
153
|
+
const listener = new ReturnPathListener(this);
|
|
154
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
155
|
+
return this.errors;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public addError(functionName: string, line: number, column: number): void {
|
|
159
|
+
this.errors.push({
|
|
160
|
+
code: "E0704",
|
|
161
|
+
functionName,
|
|
162
|
+
line,
|
|
163
|
+
column,
|
|
164
|
+
message: `Non-void function '${functionName}' must return a value on all paths`,
|
|
165
|
+
helpText:
|
|
166
|
+
"Add an explicit 'return <value>;' so every control-flow path returns a value",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default ReturnPathAnalyzer;
|
|
@@ -21,6 +21,7 @@ import ISignedShiftError from "./types/ISignedShiftError";
|
|
|
21
21
|
import ParserUtils from "../../../utils/ParserUtils";
|
|
22
22
|
import TypeConstants from "../../../utils/constants/TypeConstants";
|
|
23
23
|
import ExpressionUtils from "../../../utils/ExpressionUtils";
|
|
24
|
+
import CodeGenState from "../../state/CodeGenState";
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* First pass: Collect variable declarations with their types
|
|
@@ -28,25 +29,36 @@ import ExpressionUtils from "../../../utils/ExpressionUtils";
|
|
|
28
29
|
class SignedVariableCollector extends CNextListener {
|
|
29
30
|
private readonly signedVars: Set<string> = new Set();
|
|
30
31
|
|
|
32
|
+
// Track all variable types (for resolving struct member chains)
|
|
33
|
+
private readonly varTypes: Map<string, string> = new Map();
|
|
34
|
+
|
|
31
35
|
public getSignedVars(): Set<string> {
|
|
32
36
|
return this.signedVars;
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
public getVarTypes(): Map<string, string> {
|
|
40
|
+
return this.varTypes;
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
/**
|
|
36
|
-
* Track a typed identifier
|
|
44
|
+
* Track a typed identifier - add to signedVars if signed, always track type
|
|
37
45
|
*/
|
|
38
|
-
private
|
|
46
|
+
private trackType(
|
|
39
47
|
typeCtx: Parser.TypeContext | null,
|
|
40
48
|
identifier: { getText(): string } | null,
|
|
41
49
|
): void {
|
|
42
|
-
if (!typeCtx) return;
|
|
50
|
+
if (!typeCtx || !identifier) return;
|
|
43
51
|
|
|
44
52
|
const typeName = typeCtx.getText();
|
|
45
|
-
|
|
53
|
+
const varName = identifier.getText();
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
// Always track the variable's type for member chain resolution
|
|
56
|
+
this.varTypes.set(varName, typeName);
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
// Also track in signedVars if it's a signed type
|
|
59
|
+
if (TypeConstants.SIGNED_TYPES.includes(typeName)) {
|
|
60
|
+
this.signedVars.add(varName);
|
|
61
|
+
}
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
/**
|
|
@@ -55,21 +67,21 @@ class SignedVariableCollector extends CNextListener {
|
|
|
55
67
|
override enterVariableDeclaration = (
|
|
56
68
|
ctx: Parser.VariableDeclarationContext,
|
|
57
69
|
): void => {
|
|
58
|
-
this.
|
|
70
|
+
this.trackType(ctx.type(), ctx.IDENTIFIER());
|
|
59
71
|
};
|
|
60
72
|
|
|
61
73
|
/**
|
|
62
74
|
* Track function parameters with signed types
|
|
63
75
|
*/
|
|
64
76
|
override enterParameter = (ctx: Parser.ParameterContext): void => {
|
|
65
|
-
this.
|
|
77
|
+
this.trackType(ctx.type(), ctx.IDENTIFIER());
|
|
66
78
|
};
|
|
67
79
|
|
|
68
80
|
/**
|
|
69
81
|
* Track for-loop variable declarations with signed types
|
|
70
82
|
*/
|
|
71
83
|
override enterForVarDecl = (ctx: Parser.ForVarDeclContext): void => {
|
|
72
|
-
this.
|
|
84
|
+
this.trackType(ctx.type(), ctx.IDENTIFIER());
|
|
73
85
|
};
|
|
74
86
|
}
|
|
75
87
|
|
|
@@ -82,10 +94,18 @@ class SignedShiftListener extends CNextListener {
|
|
|
82
94
|
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
83
95
|
private readonly signedVars: Set<string>;
|
|
84
96
|
|
|
85
|
-
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
98
|
+
private readonly varTypes: Map<string, string>;
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
analyzer: SignedShiftAnalyzer,
|
|
102
|
+
signedVars: Set<string>,
|
|
103
|
+
varTypes: Map<string, string>,
|
|
104
|
+
) {
|
|
86
105
|
super();
|
|
87
106
|
this.analyzer = analyzer;
|
|
88
107
|
this.signedVars = signedVars;
|
|
108
|
+
this.varTypes = varTypes;
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
/**
|
|
@@ -116,6 +136,97 @@ class SignedShiftListener extends CNextListener {
|
|
|
116
136
|
}
|
|
117
137
|
};
|
|
118
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Check compound shift-assign statements for signed targets
|
|
141
|
+
* assignmentStatement: assignmentTarget assignmentOperator expression ';'
|
|
142
|
+
* Issue #1008: <<<- and >><- must also be rejected on signed types
|
|
143
|
+
*
|
|
144
|
+
* Handles both simple identifiers (x <<<- 2) and member chains (s.x <<<- 2)
|
|
145
|
+
*/
|
|
146
|
+
override enterAssignmentStatement = (
|
|
147
|
+
ctx: Parser.AssignmentStatementContext,
|
|
148
|
+
): void => {
|
|
149
|
+
const opCtx = ctx.assignmentOperator();
|
|
150
|
+
if (!opCtx) return;
|
|
151
|
+
|
|
152
|
+
const isLeftShiftAssign = opCtx.LSHIFT_ASSIGN() !== null;
|
|
153
|
+
const isRightShiftAssign = opCtx.RSHIFT_ASSIGN() !== null;
|
|
154
|
+
if (!isLeftShiftAssign && !isRightShiftAssign) return;
|
|
155
|
+
|
|
156
|
+
const target = ctx.assignmentTarget();
|
|
157
|
+
if (!target) return;
|
|
158
|
+
|
|
159
|
+
// Get the base identifier from the assignment target
|
|
160
|
+
const identifier = target.IDENTIFIER();
|
|
161
|
+
if (!identifier) return;
|
|
162
|
+
|
|
163
|
+
const baseName = identifier.getText();
|
|
164
|
+
const postfixOps = target.postfixTargetOp();
|
|
165
|
+
|
|
166
|
+
// Check if the final target type is signed
|
|
167
|
+
if (this.isSignedTarget(baseName, postfixOps)) {
|
|
168
|
+
const operator = isLeftShiftAssign ? "<<<-" : ">><-";
|
|
169
|
+
const { line, column } = ParserUtils.getPosition(target);
|
|
170
|
+
this.analyzer.addError(line, column, operator);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resolve the final type of an assignment target, handling member chains.
|
|
176
|
+
* Returns true if the final target is a signed type.
|
|
177
|
+
*
|
|
178
|
+
* Examples:
|
|
179
|
+
* - "x" with no postfix ops → check if x is signed
|
|
180
|
+
* - "s" with postfixOps [".x"] → check if s.x field is signed
|
|
181
|
+
* - "arr" with postfixOps ["[0]", ".field"] → check if field is signed
|
|
182
|
+
*/
|
|
183
|
+
private isSignedTarget(
|
|
184
|
+
baseName: string,
|
|
185
|
+
postfixOps: Parser.PostfixTargetOpContext[],
|
|
186
|
+
): boolean {
|
|
187
|
+
// Simple case: no member access, just a variable
|
|
188
|
+
if (postfixOps.length === 0) {
|
|
189
|
+
return this.signedVars.has(baseName);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Member chain case: resolve through the chain
|
|
193
|
+
let currentType = this.varTypes.get(baseName);
|
|
194
|
+
if (!currentType) {
|
|
195
|
+
// Unknown base type - can't resolve, skip
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Walk through the postfix operations
|
|
200
|
+
for (const op of postfixOps) {
|
|
201
|
+
const memberIdent = op.IDENTIFIER();
|
|
202
|
+
if (memberIdent) {
|
|
203
|
+
// Member access: .fieldName
|
|
204
|
+
const fieldName = memberIdent.getText();
|
|
205
|
+
const fieldType = CodeGenState.getStructFieldType(
|
|
206
|
+
currentType,
|
|
207
|
+
fieldName,
|
|
208
|
+
);
|
|
209
|
+
if (!fieldType) {
|
|
210
|
+
// Unknown field - can't resolve, skip
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
currentType = fieldType;
|
|
214
|
+
} else {
|
|
215
|
+
// Array subscript: [expr] - doesn't change the base type for primitives
|
|
216
|
+
// For arrays like u8[4], after [i] we still have u8
|
|
217
|
+
// Strip array dimensions if present
|
|
218
|
+
const bracketIndex = currentType.indexOf("[");
|
|
219
|
+
if (bracketIndex !== -1) {
|
|
220
|
+
currentType = currentType.substring(0, bracketIndex);
|
|
221
|
+
}
|
|
222
|
+
// Otherwise keep the type as-is (e.g., bit indexing on u8)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check if the final resolved type is signed
|
|
227
|
+
return TypeConstants.SIGNED_TYPES.includes(currentType);
|
|
228
|
+
}
|
|
229
|
+
|
|
119
230
|
/**
|
|
120
231
|
* Check if an additive expression contains a signed type operand
|
|
121
232
|
*/
|
|
@@ -202,13 +313,14 @@ class SignedShiftAnalyzer {
|
|
|
202
313
|
public analyze(tree: Parser.ProgramContext): ISignedShiftError[] {
|
|
203
314
|
this.errors = [];
|
|
204
315
|
|
|
205
|
-
// First pass: collect signed variables
|
|
316
|
+
// First pass: collect signed variables and all variable types
|
|
206
317
|
const collector = new SignedVariableCollector();
|
|
207
318
|
ParseTreeWalker.DEFAULT.walk(collector, tree);
|
|
208
319
|
const signedVars = collector.getSignedVars();
|
|
320
|
+
const varTypes = collector.getVarTypes();
|
|
209
321
|
|
|
210
322
|
// Second pass: detect shift with signed operands
|
|
211
|
-
const listener = new SignedShiftListener(this, signedVars);
|
|
323
|
+
const listener = new SignedShiftListener(this, signedVars, varTypes);
|
|
212
324
|
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
213
325
|
|
|
214
326
|
return this.errors;
|