c-next 0.1.1 → 0.1.3
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 +62 -4
- package/package.json +9 -13
- package/src/analysis/DivisionByZeroAnalyzer.ts +2 -2
- package/src/analysis/FloatModuloAnalyzer.ts +196 -0
- package/src/analysis/FunctionCallAnalyzer.ts +2 -4
- package/src/analysis/InitializationAnalyzer.ts +81 -9
- package/src/analysis/NullCheckAnalyzer.ts +2 -4
- package/src/analysis/types/IDeclarationInfo.ts +13 -0
- package/src/analysis/types/IDivisionByZeroError.ts +1 -1
- package/src/analysis/types/IFloatModuloError.ts +20 -0
- package/src/analysis/types/IFunctionCallError.ts +1 -1
- package/src/analysis/types/IInitializationError.ts +2 -29
- package/src/analysis/types/INullCheckError.ts +1 -1
- package/src/analysis/types/formatInitializationError.ts +20 -0
- package/src/codegen/CodeGenerator.ts +838 -78
- package/src/codegen/HeaderGenerator.ts +39 -26
- package/src/codegen/TypeResolver.ts +6 -8
- package/src/codegen/types/BITMAP_BACKING_TYPE.ts +11 -0
- package/src/codegen/types/BITMAP_SIZE.ts +11 -0
- package/src/codegen/types/C_TYPE_WIDTH.ts +31 -0
- package/src/codegen/types/FLOAT_TYPES.ts +6 -0
- package/src/codegen/types/IAssignmentContext.ts +12 -0
- package/src/codegen/types/ICodeGeneratorOptions.ts +13 -0
- package/src/codegen/types/IHeaderOptions.ts +15 -0
- package/src/codegen/types/INTEGER_TYPES.ts +8 -0
- package/src/codegen/types/SIGNED_TYPES.ts +6 -0
- package/src/codegen/types/TCodeGenContext.ts +1 -10
- package/src/codegen/types/TYPE_RANGES.ts +16 -0
- package/src/codegen/types/TYPE_WIDTH.ts +18 -0
- package/src/codegen/types/UNSIGNED_TYPES.ts +6 -0
- package/src/index.ts +4 -29
- package/src/lib/IncludeDiscovery.ts +3 -1
- package/src/lib/InputExpansion.ts +6 -2
- package/src/lib/PlatformIODetector.ts +1 -1
- package/src/lib/parse.ts +16 -0
- package/src/lib/parseWithSymbols.ts +148 -0
- package/src/lib/transpiler.ts +33 -175
- package/src/lib/types/IParseWithSymbolsResult.ts +16 -0
- package/src/lib/types/ISymbolInfo.ts +28 -0
- package/src/lib/types/ITranspileError.ts +15 -0
- package/src/lib/types/ITranspileOptions.ts +13 -0
- package/src/lib/types/ITranspileResult.ts +2 -65
- package/src/lib/types/TSymbolKind.ts +15 -0
- package/src/preprocessor/Preprocessor.ts +4 -19
- package/src/preprocessor/types/IPreprocessOptions.ts +20 -0
- package/src/preprocessor/types/IPreprocessResult.ts +1 -14
- package/src/preprocessor/types/ISourceMapping.ts +15 -0
- package/src/project/FileDiscovery.ts +3 -42
- package/src/project/Project.ts +73 -19
- package/src/project/types/EFileType.ts +13 -0
- package/src/project/types/IDiscoveredFile.ts +17 -0
- package/src/project/types/IDiscoveryOptions.ts +15 -0
- package/src/project/types/IProjectConfig.ts +0 -31
- package/src/project/types/IProjectResult.ts +27 -0
- package/src/symbols/CNextSymbolCollector.ts +19 -0
- package/src/symbols/CppSymbolCollector.ts +110 -1
- package/src/symbols/SymbolTable.ts +1 -11
- package/src/symbols/types/IStructFieldInfo.ts +11 -0
- package/src/types/ISymbol.ts +9 -0
- package/src/codegen/types/TTypeConstants.ts +0 -94
- package/src/codegen/types/index.ts +0 -17
package/README.md
CHANGED
|
@@ -301,6 +301,40 @@ Write-only registers generate optimized code:
|
|
|
301
301
|
GPIO7.DR_SET[LED_BIT] <- true; // Generates: GPIO7_DR_SET = (1 << LED_BIT);
|
|
302
302
|
```
|
|
303
303
|
|
|
304
|
+
### Slice Assignment for Memory Operations
|
|
305
|
+
|
|
306
|
+
Multi-byte copying with bounds-checked `memcpy` generation:
|
|
307
|
+
|
|
308
|
+
```cnx
|
|
309
|
+
u8 buffer[256];
|
|
310
|
+
u32 magic <- 0x12345678;
|
|
311
|
+
u8 length <- 4;
|
|
312
|
+
u32 offset <- 0;
|
|
313
|
+
|
|
314
|
+
// Copy 4 bytes from value into buffer at offset
|
|
315
|
+
buffer[offset, length] <- magic;
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Transpiles to runtime bounds-checked code:
|
|
319
|
+
|
|
320
|
+
```c
|
|
321
|
+
uint8_t buffer[256] = {0};
|
|
322
|
+
uint32_t magic = 0x12345678;
|
|
323
|
+
uint8_t length = 4;
|
|
324
|
+
uint32_t offset = 0;
|
|
325
|
+
|
|
326
|
+
if (offset + length <= sizeof(buffer)) {
|
|
327
|
+
memcpy(&buffer[offset], &magic, length);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Key Features:**
|
|
332
|
+
|
|
333
|
+
- Runtime bounds checking prevents buffer overflows
|
|
334
|
+
- Enables binary serialization and protocol implementation
|
|
335
|
+
- Works with struct fields: `buffer[offset, len] <- config.magic`
|
|
336
|
+
- Distinct from bit operations: array slices use `memcpy`, scalar bit ranges use bit manipulation
|
|
337
|
+
|
|
304
338
|
### Scopes (ADR-016)
|
|
305
339
|
|
|
306
340
|
Organize code with automatic name prefixing. Inside scopes, explicit qualification is required:
|
|
@@ -702,17 +736,41 @@ Decisions are documented in `/docs/decisions/`:
|
|
|
702
736
|
| [ADR-031](docs/decisions/adr-031-inline-functions.md) | Inline Functions | Trust compiler; `inline` is just a hint anyway |
|
|
703
737
|
| [ADR-033](docs/decisions/adr-033-packed-structs.md) | Packed Structs | Use ADR-004 register bindings or explicit serialization |
|
|
704
738
|
|
|
705
|
-
##
|
|
739
|
+
## Development
|
|
740
|
+
|
|
741
|
+
### Setup
|
|
742
|
+
|
|
743
|
+
```bash
|
|
744
|
+
# Clone and install (IMPORTANT: npm install sets up pre-commit hooks)
|
|
745
|
+
git clone https://github.com/jlaustill/c-next.git
|
|
746
|
+
cd c-next
|
|
747
|
+
npm install # Installs dependencies and Husky pre-commit hooks
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**Pre-commit hooks:** The project uses [Husky](https://typicode.github.io/husky/) to automatically format code (Prettier) and fix linting (ESLint) before every commit. This prevents formatting errors in PRs.
|
|
751
|
+
|
|
752
|
+
### Commands
|
|
706
753
|
|
|
707
754
|
```bash
|
|
708
|
-
npm run build # Full build: ANTLR + TypeScript
|
|
709
755
|
npm run antlr # Regenerate parser from grammar
|
|
710
|
-
|
|
756
|
+
npm run typecheck # Type-check TypeScript (no build required)
|
|
757
|
+
npm test # Run all tests
|
|
758
|
+
npm test -- --quiet # Minimal output (errors + summary only)
|
|
759
|
+
npm test -- tests/enum # Run specific directory
|
|
760
|
+
npm test -- tests/enum/my.test.cnx # Run single test file
|
|
761
|
+
|
|
762
|
+
# Code quality (auto-run by pre-commit hooks)
|
|
763
|
+
npm run prettier:fix # Format all code
|
|
764
|
+
npm run eslint:check # Check for lint errors
|
|
711
765
|
```
|
|
712
766
|
|
|
767
|
+
**Note:** C-Next runs directly via `tsx` without a build step. The `typecheck` command validates types only and does not generate any output files.
|
|
768
|
+
|
|
713
769
|
## Contributing
|
|
714
770
|
|
|
715
|
-
|
|
771
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow, testing requirements, and PR process.
|
|
772
|
+
|
|
773
|
+
**Quick start:** Ideas and feedback welcome via issues.
|
|
716
774
|
|
|
717
775
|
## License
|
|
718
776
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -15,17 +15,18 @@
|
|
|
15
15
|
"antlr:all": "npm run antlr && npm run antlr:c && npm run antlr:cpp",
|
|
16
16
|
"start": "tsx src/index.ts",
|
|
17
17
|
"dev": "tsx src/index.ts",
|
|
18
|
-
"
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "tsx scripts/test.ts",
|
|
19
20
|
"test:cli": "node scripts/test-cli.js",
|
|
20
|
-
"test:update": "tsx scripts/test.
|
|
21
|
+
"test:update": "tsx scripts/test.ts --update",
|
|
21
22
|
"analyze": "./scripts/static-analysis.sh",
|
|
22
23
|
"clean": "rm -rf src/parser",
|
|
23
24
|
"prettier:check": "prettier --check .",
|
|
24
25
|
"prettier:fix": "prettier --write .",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
26
|
+
"oxlint:check": "oxlint src/",
|
|
27
|
+
"oxlint:fix": "oxlint src/ --fix",
|
|
27
28
|
"prepare": "husky",
|
|
28
|
-
"prepublishOnly": "npm run prettier:check && npm run
|
|
29
|
+
"prepublishOnly": "npm run prettier:check && npm run oxlint:check && npm test"
|
|
29
30
|
},
|
|
30
31
|
"keywords": [
|
|
31
32
|
"c",
|
|
@@ -56,16 +57,11 @@
|
|
|
56
57
|
],
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@types/node": "^25.0.3",
|
|
59
|
-
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
60
|
-
"@typescript-eslint/parser": "^7.18.0",
|
|
61
60
|
"antlr4ng-cli": "^2.0.0",
|
|
62
|
-
"eslint": "^8.57.1",
|
|
63
|
-
"eslint-config-airbnb-typescript": "^18.0.0",
|
|
64
|
-
"eslint-config-prettier": "^10.1.8",
|
|
65
|
-
"eslint-plugin-import": "^2.32.0",
|
|
66
61
|
"husky": "^9.1.7",
|
|
67
62
|
"jest": "^30.2.0",
|
|
68
63
|
"lint-staged": "^16.2.7",
|
|
64
|
+
"oxlint": "^1.39.0",
|
|
69
65
|
"prettier": "^3.7.4",
|
|
70
66
|
"ts-jest": "^29.4.6"
|
|
71
67
|
},
|
|
@@ -79,7 +75,7 @@
|
|
|
79
75
|
"prettier --write --ignore-unknown"
|
|
80
76
|
],
|
|
81
77
|
"*.ts": [
|
|
82
|
-
"
|
|
78
|
+
"oxlint --fix"
|
|
83
79
|
]
|
|
84
80
|
}
|
|
85
81
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { ParseTreeWalker } from "antlr4ng";
|
|
14
14
|
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
15
15
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
16
|
-
import
|
|
16
|
+
import IDivisionByZeroError from "./types/IDivisionByZeroError";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* First pass: Collect const declarations that are zero
|
|
@@ -323,7 +323,7 @@ class DivisionByZeroListener extends CNextListener {
|
|
|
323
323
|
/**
|
|
324
324
|
* Analyzer that detects division by zero
|
|
325
325
|
*/
|
|
326
|
-
|
|
326
|
+
class DivisionByZeroAnalyzer {
|
|
327
327
|
private errors: IDivisionByZeroError[] = [];
|
|
328
328
|
|
|
329
329
|
/**
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Float Modulo Analyzer
|
|
3
|
+
* Detects modulo operator usage with floating-point types at compile time
|
|
4
|
+
*
|
|
5
|
+
* The modulo operator (%) is only valid for integer types in C.
|
|
6
|
+
* C-Next catches this early with a clear error message.
|
|
7
|
+
*
|
|
8
|
+
* Two-pass analysis:
|
|
9
|
+
* 1. Collect variable declarations with f32/f64 types
|
|
10
|
+
* 2. Detect modulo operations using float variables or literals
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ParseTreeWalker } from "antlr4ng";
|
|
14
|
+
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
15
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
16
|
+
import IFloatModuloError from "./types/IFloatModuloError";
|
|
17
|
+
|
|
18
|
+
const FLOAT_TYPES = ["f32", "f64", "float", "double"];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* First pass: Collect variable declarations with float types
|
|
22
|
+
*/
|
|
23
|
+
class FloatVariableCollector extends CNextListener {
|
|
24
|
+
private floatVars: Set<string> = new Set();
|
|
25
|
+
|
|
26
|
+
public getFloatVars(): Set<string> {
|
|
27
|
+
return this.floatVars;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Track variable declarations with f32/f64 types
|
|
32
|
+
*/
|
|
33
|
+
override enterVariableDeclaration = (
|
|
34
|
+
ctx: Parser.VariableDeclarationContext,
|
|
35
|
+
): void => {
|
|
36
|
+
const typeCtx = ctx.type();
|
|
37
|
+
if (!typeCtx) return;
|
|
38
|
+
|
|
39
|
+
const typeName = typeCtx.getText();
|
|
40
|
+
if (!FLOAT_TYPES.includes(typeName)) return;
|
|
41
|
+
|
|
42
|
+
const identifier = ctx.IDENTIFIER();
|
|
43
|
+
if (!identifier) return;
|
|
44
|
+
|
|
45
|
+
this.floatVars.add(identifier.getText());
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Track function parameters with f32/f64 types
|
|
50
|
+
*/
|
|
51
|
+
override enterParameter = (ctx: Parser.ParameterContext): void => {
|
|
52
|
+
const typeCtx = ctx.type();
|
|
53
|
+
if (!typeCtx) return;
|
|
54
|
+
|
|
55
|
+
const typeName = typeCtx.getText();
|
|
56
|
+
if (!FLOAT_TYPES.includes(typeName)) return;
|
|
57
|
+
|
|
58
|
+
const identifier = ctx.IDENTIFIER();
|
|
59
|
+
if (!identifier) return;
|
|
60
|
+
|
|
61
|
+
this.floatVars.add(identifier.getText());
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Second pass: Detect modulo operations with float operands
|
|
67
|
+
*/
|
|
68
|
+
class FloatModuloListener extends CNextListener {
|
|
69
|
+
private analyzer: FloatModuloAnalyzer;
|
|
70
|
+
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
72
|
+
private floatVars: Set<string>;
|
|
73
|
+
|
|
74
|
+
constructor(analyzer: FloatModuloAnalyzer, floatVars: Set<string>) {
|
|
75
|
+
super();
|
|
76
|
+
this.analyzer = analyzer;
|
|
77
|
+
this.floatVars = floatVars;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check multiplicative expressions for modulo with float operands
|
|
82
|
+
* multiplicativeExpression: unaryExpression (('*' | '/' | '%') unaryExpression)*
|
|
83
|
+
*/
|
|
84
|
+
override enterMultiplicativeExpression = (
|
|
85
|
+
ctx: Parser.MultiplicativeExpressionContext,
|
|
86
|
+
): void => {
|
|
87
|
+
const operands = ctx.unaryExpression();
|
|
88
|
+
if (operands.length < 2) return;
|
|
89
|
+
|
|
90
|
+
// Check each operator
|
|
91
|
+
for (let i = 0; i < operands.length - 1; i++) {
|
|
92
|
+
const operatorToken = ctx.getChild(i * 2 + 1);
|
|
93
|
+
if (!operatorToken) continue;
|
|
94
|
+
|
|
95
|
+
const operator = operatorToken.getText();
|
|
96
|
+
if (operator !== "%") continue;
|
|
97
|
+
|
|
98
|
+
const leftOperand = operands[i];
|
|
99
|
+
const rightOperand = operands[i + 1];
|
|
100
|
+
|
|
101
|
+
const leftIsFloat = this.isFloatOperand(leftOperand);
|
|
102
|
+
const rightIsFloat = this.isFloatOperand(rightOperand);
|
|
103
|
+
|
|
104
|
+
if (leftIsFloat || rightIsFloat) {
|
|
105
|
+
const line = leftOperand.start?.line ?? 0;
|
|
106
|
+
const column = leftOperand.start?.column ?? 0;
|
|
107
|
+
this.analyzer.addError(line, column);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if a unary expression is a float type
|
|
114
|
+
*/
|
|
115
|
+
private isFloatOperand(ctx: Parser.UnaryExpressionContext): boolean {
|
|
116
|
+
const postfixExpr = ctx.postfixExpression();
|
|
117
|
+
if (!postfixExpr) return false;
|
|
118
|
+
|
|
119
|
+
const primaryExpr = postfixExpr.primaryExpression();
|
|
120
|
+
if (!primaryExpr) return false;
|
|
121
|
+
|
|
122
|
+
// Check for float literal
|
|
123
|
+
const literal = primaryExpr.literal();
|
|
124
|
+
if (literal) {
|
|
125
|
+
return this.isFloatLiteral(literal);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for identifier that's a float variable
|
|
129
|
+
const identifier = primaryExpr.IDENTIFIER();
|
|
130
|
+
if (identifier) {
|
|
131
|
+
return this.floatVars.has(identifier.getText());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if a literal is a floating-point number
|
|
139
|
+
*/
|
|
140
|
+
private isFloatLiteral(ctx: Parser.LiteralContext): boolean {
|
|
141
|
+
// Check for FLOAT_LITERAL token
|
|
142
|
+
if (ctx.FLOAT_LITERAL()) return true;
|
|
143
|
+
|
|
144
|
+
// Check text for decimal point (fallback)
|
|
145
|
+
const text = ctx.getText();
|
|
146
|
+
return text.includes(".") && !text.startsWith('"');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Analyzer that detects modulo operations with floating-point types
|
|
152
|
+
*/
|
|
153
|
+
class FloatModuloAnalyzer {
|
|
154
|
+
private errors: IFloatModuloError[] = [];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Analyze the parse tree for float modulo operations
|
|
158
|
+
*/
|
|
159
|
+
public analyze(tree: Parser.ProgramContext): IFloatModuloError[] {
|
|
160
|
+
this.errors = [];
|
|
161
|
+
|
|
162
|
+
// First pass: collect float variables
|
|
163
|
+
const collector = new FloatVariableCollector();
|
|
164
|
+
ParseTreeWalker.DEFAULT.walk(collector, tree);
|
|
165
|
+
const floatVars = collector.getFloatVars();
|
|
166
|
+
|
|
167
|
+
// Second pass: detect modulo with floats
|
|
168
|
+
const listener = new FloatModuloListener(this, floatVars);
|
|
169
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
170
|
+
|
|
171
|
+
return this.errors;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Add a float modulo error
|
|
176
|
+
*/
|
|
177
|
+
public addError(line: number, column: number): void {
|
|
178
|
+
this.errors.push({
|
|
179
|
+
code: "E0804",
|
|
180
|
+
line,
|
|
181
|
+
column,
|
|
182
|
+
message: "Modulo operator not supported for floating-point types",
|
|
183
|
+
helpText:
|
|
184
|
+
"The % operator only works with integer types. Use fmod() from <math.h> for floating-point remainder.",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get all detected errors
|
|
190
|
+
*/
|
|
191
|
+
public getErrors(): IFloatModuloError[] {
|
|
192
|
+
return this.errors;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export default FloatModuloAnalyzer;
|
|
@@ -13,9 +13,7 @@ import * as Parser from "../parser/grammar/CNextParser";
|
|
|
13
13
|
import SymbolTable from "../symbols/SymbolTable";
|
|
14
14
|
import ESourceLanguage from "../types/ESourceLanguage";
|
|
15
15
|
import ESymbolKind from "../types/ESymbolKind";
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
export { IFunctionCallError };
|
|
16
|
+
import IFunctionCallError from "./types/IFunctionCallError";
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
19
|
* C-Next built-in functions
|
|
@@ -341,7 +339,7 @@ class FunctionCallListener extends CNextListener {
|
|
|
341
339
|
/**
|
|
342
340
|
* Analyzes C-Next AST for function calls before definition
|
|
343
341
|
*/
|
|
344
|
-
|
|
342
|
+
class FunctionCallAnalyzer {
|
|
345
343
|
private errors: IFunctionCallError[] = [];
|
|
346
344
|
|
|
347
345
|
/** Functions that have been defined (in order of appearance) */
|
|
@@ -14,10 +14,8 @@
|
|
|
14
14
|
import { ParseTreeWalker } from "antlr4ng";
|
|
15
15
|
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
16
16
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
IDeclarationInfo,
|
|
20
|
-
} from "./types/IInitializationError";
|
|
17
|
+
import IInitializationError from "./types/IInitializationError";
|
|
18
|
+
import IDeclarationInfo from "./types/IDeclarationInfo";
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
21
|
* Tracks the initialization state of a variable
|
|
@@ -156,14 +154,19 @@ class InitializationListener extends CNextListener {
|
|
|
156
154
|
this.analyzer.recordAssignment(name);
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
// Member access: p.x <- value (
|
|
157
|
+
// Member access: p.x <- value (struct field) or arr[i][j] <- value (multi-dim array)
|
|
160
158
|
if (targetCtx.memberAccess()) {
|
|
161
159
|
const memberCtx = targetCtx.memberAccess()!;
|
|
162
160
|
const identifiers = memberCtx.IDENTIFIER();
|
|
163
161
|
if (identifiers.length >= 2) {
|
|
162
|
+
// Struct field access: p.x <- value
|
|
164
163
|
const varName = identifiers[0].getText();
|
|
165
164
|
const fieldName = identifiers[1].getText();
|
|
166
165
|
this.analyzer.recordAssignment(varName, fieldName);
|
|
166
|
+
} else if (identifiers.length === 1) {
|
|
167
|
+
// Multi-dimensional array access: arr[i][j] <- value
|
|
168
|
+
const varName = identifiers[0].getText();
|
|
169
|
+
this.analyzer.recordAssignment(varName);
|
|
167
170
|
}
|
|
168
171
|
}
|
|
169
172
|
|
|
@@ -346,11 +349,54 @@ class InitializationListener extends CNextListener {
|
|
|
346
349
|
this.savedStates.push(this.analyzer.cloneScopeState());
|
|
347
350
|
};
|
|
348
351
|
|
|
349
|
-
|
|
350
|
-
|
|
352
|
+
/**
|
|
353
|
+
* Check if a for-loop is deterministic (will definitely run at least once)
|
|
354
|
+
* A loop is deterministic if it has the form: for (var <- 0; var < CONSTANT; ...)
|
|
355
|
+
* where CONSTANT > 0
|
|
356
|
+
*/
|
|
357
|
+
private isDeterministicForLoop(ctx: Parser.ForStatementContext): boolean {
|
|
358
|
+
const init = ctx.forInit();
|
|
359
|
+
const cond = ctx.expression();
|
|
360
|
+
|
|
361
|
+
if (!init || !cond) return false;
|
|
362
|
+
|
|
363
|
+
// Check if init is a variable declaration starting at 0
|
|
364
|
+
const forVarDecl = init.forVarDecl();
|
|
365
|
+
if (forVarDecl) {
|
|
366
|
+
const initExpr = forVarDecl.expression();
|
|
367
|
+
if (!initExpr) return false;
|
|
368
|
+
const initText = initExpr.getText();
|
|
369
|
+
if (initText !== "0") return false;
|
|
370
|
+
} else {
|
|
371
|
+
// forAssignment case: check if assigning 0
|
|
372
|
+
const forAssign = init.forAssignment();
|
|
373
|
+
if (!forAssign) return false;
|
|
374
|
+
const assignExpr = forAssign.expression();
|
|
375
|
+
if (!assignExpr) return false;
|
|
376
|
+
const assignText = assignExpr.getText();
|
|
377
|
+
if (assignText !== "0") return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check if condition is var < POSITIVE_CONSTANT
|
|
381
|
+
const condText = cond.getText();
|
|
382
|
+
// Match patterns like "i<4" or "ti<3" (no spaces in AST getText())
|
|
383
|
+
const match = condText.match(/^\w+<(\d+)$/);
|
|
384
|
+
if (!match) return false;
|
|
385
|
+
const bound = parseInt(match[1], 10);
|
|
386
|
+
return bound > 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
override exitForStatement = (ctx: Parser.ForStatementContext): void => {
|
|
351
390
|
const stateBeforeLoop = this.savedStates.pop();
|
|
352
391
|
if (stateBeforeLoop) {
|
|
353
|
-
this.
|
|
392
|
+
const isDeterministic = this.isDeterministicForLoop(ctx);
|
|
393
|
+
if (isDeterministic) {
|
|
394
|
+
// Deterministic loop - preserve initialization (loop WILL run)
|
|
395
|
+
this.analyzer.mergeInitializationState(stateBeforeLoop);
|
|
396
|
+
} else {
|
|
397
|
+
// Non-deterministic loop - conservative restore (loop might not run)
|
|
398
|
+
this.analyzer.restoreFromState(stateBeforeLoop);
|
|
399
|
+
}
|
|
354
400
|
}
|
|
355
401
|
};
|
|
356
402
|
}
|
|
@@ -358,7 +404,7 @@ class InitializationListener extends CNextListener {
|
|
|
358
404
|
/**
|
|
359
405
|
* Analyzes C-Next AST for use-before-initialization errors
|
|
360
406
|
*/
|
|
361
|
-
|
|
407
|
+
class InitializationAnalyzer {
|
|
362
408
|
private errors: IInitializationError[] = [];
|
|
363
409
|
|
|
364
410
|
private currentScope: IScope | null = null;
|
|
@@ -653,6 +699,32 @@ export class InitializationAnalyzer {
|
|
|
653
699
|
}
|
|
654
700
|
}
|
|
655
701
|
|
|
702
|
+
/**
|
|
703
|
+
* Merge initialization state from a saved snapshot
|
|
704
|
+
* Used for deterministic loops where initialization inside the loop
|
|
705
|
+
* should be preserved (the loop WILL run at least once)
|
|
706
|
+
*/
|
|
707
|
+
public mergeInitializationState(
|
|
708
|
+
beforeState: Map<string, IVariableState>,
|
|
709
|
+
): void {
|
|
710
|
+
let scope = this.currentScope;
|
|
711
|
+
while (scope) {
|
|
712
|
+
for (const [name, currentState] of scope.variables) {
|
|
713
|
+
const beforeVar = beforeState.get(name);
|
|
714
|
+
if (beforeVar) {
|
|
715
|
+
// Preserve initialization if it happened inside the loop
|
|
716
|
+
// (currentState.initialized stays true if set inside loop)
|
|
717
|
+
// Merge initializedFields from before state to preserve any
|
|
718
|
+
// fields that were initialized before the loop
|
|
719
|
+
for (const field of beforeVar.initializedFields) {
|
|
720
|
+
currentState.initializedFields.add(field);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
scope = scope.parent;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
656
728
|
// ========================================================================
|
|
657
729
|
// Error Reporting
|
|
658
730
|
// ========================================================================
|
|
@@ -16,9 +16,7 @@
|
|
|
16
16
|
import { ParseTreeWalker } from "antlr4ng";
|
|
17
17
|
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
18
18
|
import * as Parser from "../parser/grammar/CNextParser";
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
export { INullCheckError };
|
|
19
|
+
import INullCheckError from "./types/INullCheckError";
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
22
|
* Metadata for C library functions that can return NULL
|
|
@@ -284,7 +282,7 @@ class NullCheckListener extends CNextListener {
|
|
|
284
282
|
/**
|
|
285
283
|
* Analyzes C-Next AST for NULL safety violations
|
|
286
284
|
*/
|
|
287
|
-
|
|
285
|
+
class NullCheckAnalyzer {
|
|
288
286
|
private errors: INullCheckError[] = [];
|
|
289
287
|
|
|
290
288
|
/** Included headers (for context) */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information about where a variable was declared
|
|
3
|
+
*/
|
|
4
|
+
interface IDeclarationInfo {
|
|
5
|
+
/** Variable name */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Line where variable was declared */
|
|
8
|
+
line: number;
|
|
9
|
+
/** Column where variable was declared */
|
|
10
|
+
column: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default IDeclarationInfo;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error reported when modulo operator is used with floating-point types
|
|
3
|
+
*
|
|
4
|
+
* Error codes:
|
|
5
|
+
* - E0804: Modulo operator used with floating-point operand
|
|
6
|
+
*/
|
|
7
|
+
interface IFloatModuloError {
|
|
8
|
+
/** Error code (E0804) */
|
|
9
|
+
code: string;
|
|
10
|
+
/** Line number where the error occurred */
|
|
11
|
+
line: number;
|
|
12
|
+
/** Column number where the error occurred */
|
|
13
|
+
column: number;
|
|
14
|
+
/** Human-readable error message */
|
|
15
|
+
message: string;
|
|
16
|
+
/** Optional help text with suggested fix */
|
|
17
|
+
helpText?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default IFloatModuloError;
|
|
@@ -3,22 +3,12 @@
|
|
|
3
3
|
* Used for Rust-style "use before initialization" detection
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* Information about where a variable was declared
|
|
8
|
-
*/
|
|
9
|
-
export interface IDeclarationInfo {
|
|
10
|
-
/** Variable name */
|
|
11
|
-
name: string;
|
|
12
|
-
/** Line where variable was declared */
|
|
13
|
-
line: number;
|
|
14
|
-
/** Column where variable was declared */
|
|
15
|
-
column: number;
|
|
16
|
-
}
|
|
6
|
+
import IDeclarationInfo from "./IDeclarationInfo";
|
|
17
7
|
|
|
18
8
|
/**
|
|
19
9
|
* Error for using a variable before initialization
|
|
20
10
|
*/
|
|
21
|
-
|
|
11
|
+
interface IInitializationError {
|
|
22
12
|
/** Error code (E0381 matches Rust's error code) */
|
|
23
13
|
code: "E0381";
|
|
24
14
|
/** The variable or field that was used before initialization */
|
|
@@ -35,21 +25,4 @@ export interface IInitializationError {
|
|
|
35
25
|
message: string;
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
/**
|
|
39
|
-
* Format an initialization error into a Rust-style error message
|
|
40
|
-
*/
|
|
41
|
-
export function formatInitializationError(error: IInitializationError): string {
|
|
42
|
-
const certainty = error.mayBeUninitialized ? "possibly " : "";
|
|
43
|
-
return `error[${error.code}]: use of ${certainty}uninitialized variable '${error.variable}'
|
|
44
|
-
--> line ${error.line}:${error.column}
|
|
45
|
-
|
|
|
46
|
-
${error.declaration.line} | ${error.declaration.name}
|
|
47
|
-
| - variable declared here
|
|
48
|
-
...
|
|
49
|
-
${error.line} | ${error.variable}
|
|
50
|
-
| ^ use of ${certainty}uninitialized '${error.variable}'
|
|
51
|
-
|
|
|
52
|
-
= help: consider initializing '${error.variable}'`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
28
|
export default IInitializationError;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - E0903: NULL can only be used in comparison context
|
|
8
8
|
* - E0904: Cannot store C function pointer return in variable
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
interface INullCheckError {
|
|
11
11
|
/** Error code (E0901-E0904) */
|
|
12
12
|
code: string;
|
|
13
13
|
/** Name of the function or literal involved */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import IInitializationError from "./IInitializationError";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format an initialization error into a Rust-style error message
|
|
5
|
+
*/
|
|
6
|
+
function formatInitializationError(error: IInitializationError): string {
|
|
7
|
+
const certainty = error.mayBeUninitialized ? "possibly " : "";
|
|
8
|
+
return `error[${error.code}]: use of ${certainty}uninitialized variable '${error.variable}'
|
|
9
|
+
--> line ${error.line}:${error.column}
|
|
10
|
+
|
|
|
11
|
+
${error.declaration.line} | ${error.declaration.name}
|
|
12
|
+
| - variable declared here
|
|
13
|
+
...
|
|
14
|
+
${error.line} | ${error.variable}
|
|
15
|
+
| ^ use of ${certainty}uninitialized '${error.variable}'
|
|
16
|
+
|
|
|
17
|
+
= help: consider initializing '${error.variable}'`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default formatInitializationError;
|