c-next 0.2.17 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +7645 -5941
- package/dist/index.js.map +4 -4
- package/grammar/CNext.g4 +8 -0
- package/package.json +1 -3
- package/src/transpiler/Transpiler.ts +286 -26
- package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
- package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
- package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
- package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
- package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
- package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
- package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
- package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
- package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
- package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
- package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
- package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
- package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
- package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
- package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
- package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
- package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
- package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
- package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
- package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
- package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
- package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
- package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
- package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
- package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
- package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
- package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
- package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
- package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
- package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
- package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
- package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
- package/src/transpiler/state/CodeGenState.ts +20 -16
- package/src/transpiler/types/ICachedFileEntry.ts +7 -0
- package/src/utils/cache/CacheManager.ts +13 -2
- package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
- package/src/utils/constants/TypeConstants.ts +13 -0
|
@@ -155,17 +155,14 @@ const generatePostfixExpression = (
|
|
|
155
155
|
// Issue #895: Callback-compatible params need pointer semantics even in C++ mode
|
|
156
156
|
const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
|
|
157
157
|
|
|
158
|
-
// Issue #
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
} else {
|
|
167
|
-
result = orchestrator.generatePrimaryExpr(primary);
|
|
168
|
-
}
|
|
158
|
+
// Issue #1100: Subscripted parameters resolve through the normal primary
|
|
159
|
+
// expression path (ParameterDereferenceResolver), same as any other
|
|
160
|
+
// parameter reference. This is a no-op for array/struct/string params
|
|
161
|
+
// (already pointer-like, so `buf[idx]` is unaffected), and correctly
|
|
162
|
+
// dereferences a scalar parameter that became a pointer because it's
|
|
163
|
+
// modified elsewhere in the function, so bit access (`v[4]`) reads
|
|
164
|
+
// through the pointer instead of pointer-indexing past it.
|
|
165
|
+
const result: string = orchestrator.generatePrimaryExpr(primary);
|
|
169
166
|
|
|
170
167
|
const primaryTypeInfo = rootIdentifier
|
|
171
168
|
? CodeGenState.getVariableTypeInfo(rootIdentifier)
|
|
@@ -1805,6 +1802,14 @@ const handleBitRangeSubscript = (
|
|
|
1805
1802
|
orchestrator.generateExpression(exprs[1]),
|
|
1806
1803
|
]);
|
|
1807
1804
|
|
|
1805
|
+
// Issue #1094: resolve a const/macro width to its numeric value so the mask is
|
|
1806
|
+
// precomputed (byte-identical to a literal width) instead of a runtime
|
|
1807
|
+
// ((1U << W) - 1) — which is UB at full width (1U << 32) and uses the wrong
|
|
1808
|
+
// base type for >32-bit widths. The "U" suffix matches the literal path, which
|
|
1809
|
+
// generates bit widths under a size_t expectedType.
|
|
1810
|
+
const widthConst = orchestrator.tryEvaluateConstant(exprs[1]);
|
|
1811
|
+
const maskWidth = widthConst === undefined ? width : `${widthConst}U`;
|
|
1812
|
+
|
|
1808
1813
|
const isFloatType =
|
|
1809
1814
|
ctx.primaryTypeInfo?.baseType === "f32" ||
|
|
1810
1815
|
ctx.primaryTypeInfo?.baseType === "f64";
|
|
@@ -1817,13 +1822,19 @@ const handleBitRangeSubscript = (
|
|
|
1817
1822
|
baseType: ctx.primaryTypeInfo!.baseType,
|
|
1818
1823
|
start,
|
|
1819
1824
|
width,
|
|
1825
|
+
maskWidth,
|
|
1820
1826
|
},
|
|
1821
1827
|
state,
|
|
1822
1828
|
orchestrator,
|
|
1823
1829
|
effects,
|
|
1824
1830
|
);
|
|
1825
1831
|
} else {
|
|
1826
|
-
|
|
1832
|
+
// Issue #1094: 64-bit operands need a 64-bit mask base (1ULL / wide hex) so
|
|
1833
|
+
// widths > 32 don't shift past a 32-bit literal's width.
|
|
1834
|
+
const is64BitOperand =
|
|
1835
|
+
ctx.primaryTypeInfo?.baseType === "u64" ||
|
|
1836
|
+
ctx.primaryTypeInfo?.baseType === "i64";
|
|
1837
|
+
const mask = orchestrator.generateBitMask(maskWidth, is64BitOperand);
|
|
1827
1838
|
// Skip shift when start is 0 (either "0" or "0U" with MISRA suffix)
|
|
1828
1839
|
let expr: string;
|
|
1829
1840
|
if (start === "0" || start === "0U") {
|
|
@@ -1861,6 +1872,8 @@ interface IFloatBitRangeContext {
|
|
|
1861
1872
|
baseType: string;
|
|
1862
1873
|
start: string;
|
|
1863
1874
|
width: string;
|
|
1875
|
+
/** Width formatted for mask generation (const-resolved when possible, #1094). */
|
|
1876
|
+
maskWidth: string;
|
|
1864
1877
|
}
|
|
1865
1878
|
|
|
1866
1879
|
/**
|
|
@@ -1892,7 +1905,7 @@ const handleFloatBitRange = (
|
|
|
1892
1905
|
const floatType = getFloatTypeName(ctx.baseType);
|
|
1893
1906
|
const intType = isF64 ? "uint64_t" : "uint32_t";
|
|
1894
1907
|
const shadowName = `__bits_${ctx.rootIdentifier}`;
|
|
1895
|
-
const mask = orchestrator.generateBitMask(ctx.
|
|
1908
|
+
const mask = orchestrator.generateBitMask(ctx.maskWidth, isF64);
|
|
1896
1909
|
|
|
1897
1910
|
const needsDeclaration = !orchestrator.hasFloatBitShadow(shadowName);
|
|
1898
1911
|
if (needsDeclaration) {
|
|
@@ -147,6 +147,7 @@ function createMockOrchestrator(overrides?: {
|
|
|
147
147
|
) => TTypeInfo | null;
|
|
148
148
|
validateCrossScopeVisibility?: (scope: string, member: string) => void;
|
|
149
149
|
generateBitMask?: (width: string, is64?: boolean) => string;
|
|
150
|
+
tryEvaluateConstant?: (ctx: unknown) => number | undefined;
|
|
150
151
|
hasFloatBitShadow?: (name: string) => boolean;
|
|
151
152
|
registerFloatBitShadow?: (name: string) => void;
|
|
152
153
|
addPendingTempDeclaration?: (decl: string) => void;
|
|
@@ -172,7 +173,7 @@ function createMockOrchestrator(overrides?: {
|
|
|
172
173
|
isCNextFunction: vi.fn(),
|
|
173
174
|
isStructType: vi.fn(),
|
|
174
175
|
getTypeName: vi.fn(),
|
|
175
|
-
tryEvaluateConstant: vi.fn(),
|
|
176
|
+
tryEvaluateConstant: overrides?.tryEvaluateConstant ?? vi.fn(),
|
|
176
177
|
getZeroInitializer: vi.fn(),
|
|
177
178
|
getExpressionEnumType: vi.fn(),
|
|
178
179
|
isIntegerExpression: vi.fn(),
|
|
@@ -1265,6 +1266,98 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1265
1266
|
const result = generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1266
1267
|
expect(result.code).toBe("((val) & 0xFF)");
|
|
1267
1268
|
});
|
|
1269
|
+
|
|
1270
|
+
// Issue #1094: a const/macro width must be resolved to its numeric value (with
|
|
1271
|
+
// a "U" suffix to match the literal path) before mask generation, instead of
|
|
1272
|
+
// being passed through as the identifier — otherwise the mask falls back to a
|
|
1273
|
+
// runtime ((1U << W) - 1) that is UB at full width.
|
|
1274
|
+
it("resolves a const width to a precomputed mask (#1094)", () => {
|
|
1275
|
+
const typeRegistry = new Map<string, TTypeInfo>([
|
|
1276
|
+
[
|
|
1277
|
+
"val",
|
|
1278
|
+
{ baseType: "u32", bitWidth: 32, isArray: false, isConst: false },
|
|
1279
|
+
],
|
|
1280
|
+
]);
|
|
1281
|
+
const ctx = createMockPostfixExpressionContext("val", [
|
|
1282
|
+
createMockPostfixOp({
|
|
1283
|
+
expressions: [
|
|
1284
|
+
createMockExpression("0"),
|
|
1285
|
+
createMockExpression("WIDTH"),
|
|
1286
|
+
],
|
|
1287
|
+
}),
|
|
1288
|
+
]);
|
|
1289
|
+
const input = createMockInput({ typeRegistry });
|
|
1290
|
+
const state = createMockState();
|
|
1291
|
+
const generateBitMask = vi.fn(() => "0xFFFFFFFFU");
|
|
1292
|
+
const orchestrator = createMockOrchestrator({
|
|
1293
|
+
generatePrimaryExpr: () => "val",
|
|
1294
|
+
generateExpression: (ctx) => ctx.getText(),
|
|
1295
|
+
generateBitMask,
|
|
1296
|
+
tryEvaluateConstant: () => 32,
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
const result = generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1300
|
+
// Width resolved to "32U" (not the identifier "WIDTH"); u32 operand → not 64-bit.
|
|
1301
|
+
expect(generateBitMask).toHaveBeenCalledWith("32U", false);
|
|
1302
|
+
expect(result.code).toBe("((val) & 0xFFFFFFFFU)");
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
it("passes the raw width when it is not a const (#1094)", () => {
|
|
1306
|
+
const typeRegistry = new Map<string, TTypeInfo>([
|
|
1307
|
+
[
|
|
1308
|
+
"val",
|
|
1309
|
+
{ baseType: "u32", bitWidth: 32, isArray: false, isConst: false },
|
|
1310
|
+
],
|
|
1311
|
+
]);
|
|
1312
|
+
const ctx = createMockPostfixExpressionContext("val", [
|
|
1313
|
+
createMockPostfixOp({
|
|
1314
|
+
expressions: [createMockExpression("0"), createMockExpression("n")],
|
|
1315
|
+
}),
|
|
1316
|
+
]);
|
|
1317
|
+
const input = createMockInput({ typeRegistry });
|
|
1318
|
+
const state = createMockState();
|
|
1319
|
+
const generateBitMask = vi.fn(() => "((1U << n) - 1)");
|
|
1320
|
+
const orchestrator = createMockOrchestrator({
|
|
1321
|
+
generatePrimaryExpr: () => "val",
|
|
1322
|
+
generateExpression: (ctx) => ctx.getText(),
|
|
1323
|
+
generateBitMask,
|
|
1324
|
+
tryEvaluateConstant: () => undefined,
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1328
|
+
// Non-const width: the generated expression string flows through unchanged.
|
|
1329
|
+
expect(generateBitMask).toHaveBeenCalledWith("n", false);
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
it("marks a u64 operand as 64-bit for the mask base (#1094)", () => {
|
|
1333
|
+
const typeRegistry = new Map<string, TTypeInfo>([
|
|
1334
|
+
[
|
|
1335
|
+
"val",
|
|
1336
|
+
{ baseType: "u64", bitWidth: 64, isArray: false, isConst: false },
|
|
1337
|
+
],
|
|
1338
|
+
]);
|
|
1339
|
+
const ctx = createMockPostfixExpressionContext("val", [
|
|
1340
|
+
createMockPostfixOp({
|
|
1341
|
+
expressions: [
|
|
1342
|
+
createMockExpression("0"),
|
|
1343
|
+
createMockExpression("WIDTH"),
|
|
1344
|
+
],
|
|
1345
|
+
}),
|
|
1346
|
+
]);
|
|
1347
|
+
const input = createMockInput({ typeRegistry });
|
|
1348
|
+
const state = createMockState();
|
|
1349
|
+
const generateBitMask = vi.fn(() => "((1ULL << 40U) - 1)");
|
|
1350
|
+
const orchestrator = createMockOrchestrator({
|
|
1351
|
+
generatePrimaryExpr: () => "val",
|
|
1352
|
+
generateExpression: (ctx) => ctx.getText(),
|
|
1353
|
+
generateBitMask,
|
|
1354
|
+
tryEvaluateConstant: () => 40,
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1358
|
+
// u64 operand → is64Bit true, so the mask uses a 64-bit base (1ULL).
|
|
1359
|
+
expect(generateBitMask).toHaveBeenCalledWith("40U", true);
|
|
1360
|
+
});
|
|
1268
1361
|
});
|
|
1269
1362
|
|
|
1270
1363
|
describe("float bit indexing", () => {
|
|
@@ -1371,6 +1464,41 @@ describe("PostfixExpressionGenerator", () => {
|
|
|
1371
1464
|
// Uses union member .u for bit access
|
|
1372
1465
|
expect(result.code).toBe("(__bits_f.u & 0xFF)");
|
|
1373
1466
|
});
|
|
1467
|
+
|
|
1468
|
+
// Issue #1094: the float branch must resolve a const width too, and tell the
|
|
1469
|
+
// mask generator the f64 union is 64-bit.
|
|
1470
|
+
it("resolves a const width for f64 bit indexing (#1094)", () => {
|
|
1471
|
+
const typeRegistry = new Map<string, TTypeInfo>([
|
|
1472
|
+
[
|
|
1473
|
+
"d",
|
|
1474
|
+
{ baseType: "f64", bitWidth: 64, isArray: false, isConst: false },
|
|
1475
|
+
],
|
|
1476
|
+
]);
|
|
1477
|
+
const ctx = createMockPostfixExpressionContext("d", [
|
|
1478
|
+
createMockPostfixOp({
|
|
1479
|
+
expressions: [
|
|
1480
|
+
createMockExpression("0"),
|
|
1481
|
+
createMockExpression("WIDTH"),
|
|
1482
|
+
],
|
|
1483
|
+
}),
|
|
1484
|
+
]);
|
|
1485
|
+
const input = createMockInput({ typeRegistry });
|
|
1486
|
+
const state = createMockState({ inFunctionBody: true });
|
|
1487
|
+
const generateBitMask = vi.fn(() => "0xFFFFFFFFFFFFFFFFULL");
|
|
1488
|
+
const orchestrator = createMockOrchestrator({
|
|
1489
|
+
generatePrimaryExpr: () => "d",
|
|
1490
|
+
generateExpression: (ctx) => ctx.getText(),
|
|
1491
|
+
generateBitMask,
|
|
1492
|
+
tryEvaluateConstant: () => 64,
|
|
1493
|
+
hasFloatBitShadow: () => true,
|
|
1494
|
+
isFloatShadowCurrent: () => true,
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
const result = generatePostfixExpression(ctx, input, state, orchestrator);
|
|
1498
|
+
// Const width "64U" passed; f64 union → 64-bit mask base.
|
|
1499
|
+
expect(generateBitMask).toHaveBeenCalledWith("64U", true);
|
|
1500
|
+
expect(result.code).toBe("(__bits_d.u & 0xFFFFFFFFFFFFFFFFULL)");
|
|
1501
|
+
});
|
|
1374
1502
|
});
|
|
1375
1503
|
|
|
1376
1504
|
describe("function call", () => {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
WhileStatementContext,
|
|
15
15
|
DoWhileStatementContext,
|
|
16
16
|
ForStatementContext,
|
|
17
|
+
ForeverStatementContext,
|
|
17
18
|
ForVarDeclContext,
|
|
18
19
|
ForAssignmentContext,
|
|
19
20
|
ExpressionContext,
|
|
@@ -167,6 +168,9 @@ const generateWhile = (
|
|
|
167
168
|
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
168
169
|
orchestrator.validateConditionIsBoolean(node.expression(), "while");
|
|
169
170
|
|
|
171
|
+
// ADR-113 / #1075: reject always-true literal condition (E0707)
|
|
172
|
+
orchestrator.validateLoopConditionNotAlwaysTrue(node.expression());
|
|
173
|
+
|
|
170
174
|
const condition = orchestrator.generateExpression(node.expression());
|
|
171
175
|
|
|
172
176
|
// Issue #250: Flush any temp vars from condition BEFORE generating body
|
|
@@ -201,6 +205,9 @@ const generateDoWhile = (
|
|
|
201
205
|
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
202
206
|
orchestrator.validateConditionIsBoolean(node.expression(), "do-while");
|
|
203
207
|
|
|
208
|
+
// ADR-113 / #1075: reject always-true literal condition (E0707)
|
|
209
|
+
orchestrator.validateLoopConditionNotAlwaysTrue(node.expression());
|
|
210
|
+
|
|
204
211
|
const body = orchestrator.generateBlock(node.block());
|
|
205
212
|
const condition = orchestrator.generateExpression(node.expression());
|
|
206
213
|
|
|
@@ -286,6 +293,15 @@ const generateFor = (
|
|
|
286
293
|
): IGeneratorOutput => {
|
|
287
294
|
const effects: TGeneratorEffect[] = [];
|
|
288
295
|
|
|
296
|
+
// ADR-113 / #1075 E0707: a for-loop with no controlling expression (`for (;;)`)
|
|
297
|
+
// is a disguised infinite loop. C-Next has one source form for that — `forever`.
|
|
298
|
+
if (!node.expression()) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
"Error E0707: for-loop has no controlling expression (infinite loop)\n" +
|
|
301
|
+
" help: write 'forever { ... }' for an intentional infinite loop",
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
289
305
|
let init = "";
|
|
290
306
|
const forInit = node.forInit();
|
|
291
307
|
if (forInit) {
|
|
@@ -313,15 +329,20 @@ const generateFor = (
|
|
|
313
329
|
// Issue #250: Flush temps from init before generating condition
|
|
314
330
|
const initTemps = orchestrator.flushPendingTempDeclarations();
|
|
315
331
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
orchestrator.validateConditionNoFunctionCall(node.expression()!, "for");
|
|
332
|
+
// The empty-header case (`for (;;)`) already threw E0707 above, so the
|
|
333
|
+
// controlling expression is guaranteed present here.
|
|
334
|
+
const conditionExpr = node.expression()!;
|
|
320
335
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
336
|
+
// Issue #254: Validate no function calls in condition (E0702)
|
|
337
|
+
orchestrator.validateConditionNoFunctionCall(conditionExpr, "for");
|
|
338
|
+
|
|
339
|
+
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
340
|
+
orchestrator.validateConditionIsBoolean(conditionExpr, "for");
|
|
341
|
+
|
|
342
|
+
// ADR-113 / #1075: reject always-true literal condition (E0707)
|
|
343
|
+
orchestrator.validateLoopConditionNotAlwaysTrue(conditionExpr);
|
|
344
|
+
|
|
345
|
+
const condition = orchestrator.generateExpression(conditionExpr);
|
|
325
346
|
|
|
326
347
|
// Issue #250: Flush temps from condition before generating update
|
|
327
348
|
const conditionTemps = orchestrator.flushPendingTempDeclarations();
|
|
@@ -357,6 +378,40 @@ const generateFor = (
|
|
|
357
378
|
return { code: result, effects };
|
|
358
379
|
};
|
|
359
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Generate C code for a forever statement (ADR-113).
|
|
383
|
+
*
|
|
384
|
+
* Lowers to the MISRA C:2012 Rule 14.3-compliant infinite-loop idiom `for (;;)`
|
|
385
|
+
* (the carve-out that rule explicitly permits — no controlling expression to be
|
|
386
|
+
* flagged as invariant). A `forever` loop may appear only in a void function
|
|
387
|
+
* (E0705): a value-returning function can never honor its return type if it
|
|
388
|
+
* loops forever.
|
|
389
|
+
*/
|
|
390
|
+
const generateForever = (
|
|
391
|
+
node: ForeverStatementContext,
|
|
392
|
+
_input: IGeneratorInput,
|
|
393
|
+
_state: IGeneratorState,
|
|
394
|
+
orchestrator: IOrchestrator,
|
|
395
|
+
): IGeneratorOutput => {
|
|
396
|
+
const effects: TGeneratorEffect[] = [];
|
|
397
|
+
|
|
398
|
+
// ADR-113 E0705: forever is void-only.
|
|
399
|
+
const returnType = orchestrator.getCurrentFunctionReturnType();
|
|
400
|
+
if (returnType && returnType !== "void") {
|
|
401
|
+
throw new Error(
|
|
402
|
+
"Error E0705: forever loop in non-void function\n" +
|
|
403
|
+
" help: a forever loop never returns a value; make the function return void, " +
|
|
404
|
+
"or use a while loop with an exit condition",
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const body = orchestrator.generateBlock(node.block());
|
|
409
|
+
const comment =
|
|
410
|
+
"/* MISRA C:2012 Rule 14.3: infinite loop is intentional (C-Next `forever`) */";
|
|
411
|
+
|
|
412
|
+
return { code: `${comment}\nfor (;;) ${body}`, effects };
|
|
413
|
+
};
|
|
414
|
+
|
|
360
415
|
// Export all control flow generators
|
|
361
416
|
const controlFlowGenerators = {
|
|
362
417
|
generateReturn,
|
|
@@ -364,6 +419,7 @@ const controlFlowGenerators = {
|
|
|
364
419
|
generateWhile,
|
|
365
420
|
generateDoWhile,
|
|
366
421
|
generateFor,
|
|
422
|
+
generateForever,
|
|
367
423
|
generateForVarDecl,
|
|
368
424
|
generateForAssignment,
|
|
369
425
|
};
|
package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts
CHANGED
|
@@ -365,6 +365,7 @@ function createMockOrchestrator(options?: {
|
|
|
365
365
|
flushPendingTempDeclarations: vi.fn(() => options?.tempDeclarations ?? ""),
|
|
366
366
|
validateConditionNoFunctionCall: vi.fn(),
|
|
367
367
|
validateConditionIsBoolean: vi.fn(),
|
|
368
|
+
validateLoopConditionNotAlwaysTrue: vi.fn(),
|
|
368
369
|
countStringLengthAccesses: vi.fn(() => new Map()),
|
|
369
370
|
countBlockLengthAccesses: vi.fn(),
|
|
370
371
|
setupLengthCache: vi.fn(() => options?.lengthCacheDecls ?? ""),
|
|
@@ -898,15 +899,15 @@ describe("ControlFlowGenerator", () => {
|
|
|
898
899
|
// ========================================================================
|
|
899
900
|
|
|
900
901
|
describe("generateFor", () => {
|
|
901
|
-
it("
|
|
902
|
-
const ctx = createMockForStatement();
|
|
902
|
+
it("rejects an empty for(;;) header as a disguised infinite loop (ADR-113 / #1075, E0707)", () => {
|
|
903
|
+
const ctx = createMockForStatement(); // no controlling expression
|
|
903
904
|
const input = createMockInput();
|
|
904
905
|
const state = createMockState();
|
|
905
906
|
const orchestrator = createMockOrchestrator({ statementCode: "{ }" });
|
|
906
907
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
908
|
+
expect(() => generateFor(ctx, input, state, orchestrator)).toThrow(
|
|
909
|
+
/E0707: for-loop has no controlling expression/,
|
|
910
|
+
);
|
|
910
911
|
});
|
|
911
912
|
|
|
912
913
|
it("generates for loop with all parts", () => {
|
|
@@ -947,6 +948,7 @@ describe("ControlFlowGenerator", () => {
|
|
|
947
948
|
operator: createMockAssignmentOperator("<-"),
|
|
948
949
|
}),
|
|
949
950
|
}),
|
|
951
|
+
expr: createMockExpression({ text: "i < 10" }),
|
|
950
952
|
});
|
|
951
953
|
const input = createMockInput();
|
|
952
954
|
const state = createMockState();
|
|
@@ -1007,6 +1009,7 @@ describe("ControlFlowGenerator", () => {
|
|
|
1007
1009
|
init: createMockForInit({
|
|
1008
1010
|
varDecl: createMockForVarDecl(),
|
|
1009
1011
|
}),
|
|
1012
|
+
expr: createMockExpression({ text: "i < 10" }),
|
|
1010
1013
|
});
|
|
1011
1014
|
const input = createMockInput();
|
|
1012
1015
|
const state = createMockState();
|
|
@@ -71,7 +71,10 @@ class AssignmentExpectedTypeResolver {
|
|
|
71
71
|
// Case 2b: Simple array element access (arr[i] <- value)
|
|
72
72
|
// Issue #872: Resolve element type for MISRA 7.2 U suffix
|
|
73
73
|
if (identifiers.length === 1 && hasSubscript) {
|
|
74
|
-
return AssignmentExpectedTypeResolver.resolveForArrayElement(
|
|
74
|
+
return AssignmentExpectedTypeResolver.resolveForArrayElement(
|
|
75
|
+
baseId,
|
|
76
|
+
postfixOps,
|
|
77
|
+
);
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
// Case 2c: Member chain with array access (struct.arr[i] <- value)
|
|
@@ -87,6 +90,17 @@ class AssignmentExpectedTypeResolver {
|
|
|
87
90
|
return { expectedType: null, assignmentContext: null };
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
/**
|
|
94
|
+
* True if any postfix subscript is the 2-expression `[offset, length]` form —
|
|
95
|
+
* an array slice or scalar bit-range write (vs a 1-expression element/bit
|
|
96
|
+
* access). The grammar models both as `'[' expression ',' expression ']'`.
|
|
97
|
+
*/
|
|
98
|
+
private static hasRangeSubscript(
|
|
99
|
+
postfixOps: Parser.PostfixTargetOpContext[],
|
|
100
|
+
): boolean {
|
|
101
|
+
return postfixOps.some((op) => op.expression().length === 2);
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
/**
|
|
91
105
|
* Resolve expected type for a simple identifier target.
|
|
92
106
|
*/
|
|
@@ -124,13 +138,27 @@ class AssignmentExpectedTypeResolver {
|
|
|
124
138
|
/**
|
|
125
139
|
* Resolve expected type for array element access.
|
|
126
140
|
* Issue #872: arr[i] <- value needs baseType for MISRA 7.2 U suffix.
|
|
141
|
+
*
|
|
142
|
+
* An array SLICE (2-expression subscript `arr[off, len]`) is the exception:
|
|
143
|
+
* its source serializes at the SOURCE's own width, so leaking the element type
|
|
144
|
+
* as expectedType truncates an element-width-sensitive source such as a
|
|
145
|
+
* bit-extraction (Issue #1085: `buf[0,4] <- b[0,32]` wrote only the low byte).
|
|
146
|
+
* This applies only to arrays — a 2-expression subscript on a scalar is a
|
|
147
|
+
* bit-range write, whose value is genuinely the field's type (unchanged).
|
|
127
148
|
*/
|
|
128
|
-
private static resolveForArrayElement(
|
|
149
|
+
private static resolveForArrayElement(
|
|
150
|
+
id: string,
|
|
151
|
+
postfixOps: Parser.PostfixTargetOpContext[],
|
|
152
|
+
): IExpectedTypeResult {
|
|
129
153
|
const typeInfo = CodeGenState.getVariableTypeInfo(id);
|
|
130
154
|
if (!typeInfo?.isArray) {
|
|
131
155
|
return { expectedType: null, assignmentContext: null };
|
|
132
156
|
}
|
|
133
157
|
|
|
158
|
+
if (AssignmentExpectedTypeResolver.hasRangeSubscript(postfixOps)) {
|
|
159
|
+
return { expectedType: null, assignmentContext: null };
|
|
160
|
+
}
|
|
161
|
+
|
|
134
162
|
// Element type is the baseType (e.g., u8[10] -> "u8")
|
|
135
163
|
return { expectedType: typeInfo.baseType, assignmentContext: null };
|
|
136
164
|
}
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import * as Parser from "../../../logic/parser/grammar/CNextParser.js";
|
|
16
|
-
import FormatUtils from "../../../../utils/FormatUtils.js";
|
|
17
16
|
import StringUtils from "../../../../utils/StringUtils.js";
|
|
18
17
|
import CodeGenState from "../../../state/CodeGenState.js";
|
|
19
18
|
|
|
@@ -412,7 +411,10 @@ class StringDeclHelper {
|
|
|
412
411
|
return { code, handled: true };
|
|
413
412
|
}
|
|
414
413
|
|
|
415
|
-
// String variable:
|
|
414
|
+
// String variable: cannot use C array initialization, so declare empty and
|
|
415
|
+
// copy. Issue #1044: use the same bounded copy as the reassignment path
|
|
416
|
+
// (strncpy + explicit null terminator via StringUtils.copyWithNull) rather
|
|
417
|
+
// than an unbounded strcpy, which flawfinder flags as CWE-120.
|
|
416
418
|
// Issue #1030: string-to-string initialization
|
|
417
419
|
if (!CodeGenState.inFunctionBody) {
|
|
418
420
|
throw new Error(
|
|
@@ -422,11 +424,12 @@ class StringDeclHelper {
|
|
|
422
424
|
}
|
|
423
425
|
|
|
424
426
|
const srcExpr = callbacks.generateExpression(expression);
|
|
425
|
-
|
|
427
|
+
// Issue #1037: continuation lines carry no indent of their own — the block
|
|
428
|
+
// emitter (CodeGenerator.generateBlock) prefixes every line.
|
|
426
429
|
const lines: string[] = [];
|
|
427
430
|
lines.push(
|
|
428
431
|
`${constMod}char ${name}[${capacity + 1}] = "";`,
|
|
429
|
-
|
|
432
|
+
StringUtils.copyWithNull(name, srcExpr, capacity),
|
|
430
433
|
);
|
|
431
434
|
return { code: lines.join("\n"), handled: true };
|
|
432
435
|
}
|
|
@@ -637,14 +640,14 @@ class StringDeclHelper {
|
|
|
637
640
|
);
|
|
638
641
|
}
|
|
639
642
|
|
|
640
|
-
// Generate safe concatenation code
|
|
641
|
-
|
|
643
|
+
// Generate safe concatenation code. Issue #1037: continuation lines carry
|
|
644
|
+
// no indent of their own — the block emitter prefixes every line.
|
|
642
645
|
const lines: string[] = [];
|
|
643
646
|
lines.push(
|
|
644
647
|
`${constMod}char ${name}[${capacity + 1}] = "";`,
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
`${
|
|
648
|
+
`strncpy(${name}, ${concatOps.left}, ${capacity});`,
|
|
649
|
+
`strncat(${name}, ${concatOps.right}, ${capacity} - strlen(${name}));`,
|
|
650
|
+
`${name}[${capacity}] = ${C_NULL_CHAR};`,
|
|
648
651
|
);
|
|
649
652
|
return { code: lines.join("\n"), handled: true };
|
|
650
653
|
}
|
|
@@ -688,13 +691,13 @@ class StringDeclHelper {
|
|
|
688
691
|
);
|
|
689
692
|
}
|
|
690
693
|
|
|
691
|
-
// Generate safe substring extraction code
|
|
692
|
-
|
|
694
|
+
// Generate safe substring extraction code. Issue #1037: continuation lines
|
|
695
|
+
// carry no indent of their own — the block emitter prefixes every line.
|
|
693
696
|
const lines: string[] = [];
|
|
694
697
|
lines.push(
|
|
695
698
|
`${constMod}char ${name}[${capacity + 1}] = "";`,
|
|
696
|
-
|
|
697
|
-
`${
|
|
699
|
+
`strncpy(${name}, ${substringOps.source} + ${substringOps.start}, ${substringOps.length});`,
|
|
700
|
+
`${name}[${substringOps.length}] = ${C_NULL_CHAR};`,
|
|
698
701
|
);
|
|
699
702
|
return { code: lines.join("\n"), handled: true };
|
|
700
703
|
}
|
package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts
CHANGED
|
@@ -256,6 +256,25 @@ describe("AssignmentExpectedTypeResolver", () => {
|
|
|
256
256
|
|
|
257
257
|
expect(result.expectedType).toBeNull();
|
|
258
258
|
});
|
|
259
|
+
|
|
260
|
+
// Issue #1085: an array SLICE (2-expression subscript `arr[off, len]`)
|
|
261
|
+
// serializes the source at the SOURCE's own width. Leaking the element type
|
|
262
|
+
// as expectedType truncates a wider source (e.g. a bit-extraction), so the
|
|
263
|
+
// resolver must return null for the slice form — unlike a 1-expression
|
|
264
|
+
// element access, which keeps the element type for the MISRA 7.2 U suffix.
|
|
265
|
+
it("should return null for an array slice (2-expression subscript)", () => {
|
|
266
|
+
CodeGenState.setVariableTypeInfo("buffer", {
|
|
267
|
+
baseType: "u8",
|
|
268
|
+
bitWidth: 8,
|
|
269
|
+
isArray: true,
|
|
270
|
+
isConst: false,
|
|
271
|
+
});
|
|
272
|
+
const target = parseAssignmentTarget("buffer[0, 4]");
|
|
273
|
+
|
|
274
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
275
|
+
|
|
276
|
+
expect(result.expectedType).toBeNull();
|
|
277
|
+
});
|
|
259
278
|
});
|
|
260
279
|
});
|
|
261
280
|
});
|
|
@@ -310,7 +310,8 @@ describe("StringDeclHelper", () => {
|
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it("allows assignment when source capacity fits", () => {
|
|
313
|
-
// Issue #
|
|
313
|
+
// Issue #1044: String variable initialization uses a bounded strncpy
|
|
314
|
+
// (shared with the reassignment path), not an unsafe strcpy (CWE-120).
|
|
314
315
|
const callbacks = {
|
|
315
316
|
...defaultCallbacks,
|
|
316
317
|
getStringExprCapacity: vi.fn(() => 20),
|
|
@@ -338,9 +339,49 @@ describe("StringDeclHelper", () => {
|
|
|
338
339
|
);
|
|
339
340
|
|
|
340
341
|
expect(result.handled).toBe(true);
|
|
341
|
-
// Issue #
|
|
342
|
+
// Issue #1044: bounded copy, not strcpy
|
|
342
343
|
expect(result.code).toContain('char dest[33] = "";');
|
|
343
|
-
expect(result.code).toContain("
|
|
344
|
+
expect(result.code).toContain("strncpy(dest, smallString, 32);");
|
|
345
|
+
expect(result.code).toContain("dest[32] = '\\0';");
|
|
346
|
+
expect(result.code).not.toContain("strcpy(dest, smallString)");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("does not indent continuation lines (the block emitter owns indentation) — Issue #1037", () => {
|
|
350
|
+
// indentLevel = 1 (beforeEach). generateBlock prefixes every line of a
|
|
351
|
+
// statement, so the helper must NOT add its own indent or continuation
|
|
352
|
+
// lines double-indent.
|
|
353
|
+
const callbacks = {
|
|
354
|
+
...defaultCallbacks,
|
|
355
|
+
getStringExprCapacity: vi.fn(() => 20),
|
|
356
|
+
generateExpression: vi.fn(() => "smallString"),
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const typeCtx = {
|
|
360
|
+
stringType: () => ({
|
|
361
|
+
INTEGER_LITERAL: () => ({ getText: () => "32" }),
|
|
362
|
+
}),
|
|
363
|
+
} as never;
|
|
364
|
+
|
|
365
|
+
const expression = {
|
|
366
|
+
getText: () => "smallString",
|
|
367
|
+
} as never;
|
|
368
|
+
|
|
369
|
+
const result = StringDeclHelper.generateStringDecl(
|
|
370
|
+
typeCtx,
|
|
371
|
+
"dest",
|
|
372
|
+
expression,
|
|
373
|
+
[],
|
|
374
|
+
{ extern: "", const: "", atomic: "", volatile: "" },
|
|
375
|
+
false,
|
|
376
|
+
callbacks,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const lines = result.code.split("\n");
|
|
380
|
+
expect(lines[0]).toBe('char dest[33] = "";');
|
|
381
|
+
// Continuation line must have no leading whitespace of its own.
|
|
382
|
+
expect(lines[1]).toBe(
|
|
383
|
+
"strncpy(dest, smallString, 32); dest[32] = '\\0';",
|
|
384
|
+
);
|
|
344
385
|
});
|
|
345
386
|
|
|
346
387
|
it("throws error for string variable initialization at global scope", () => {
|
|
@@ -411,12 +452,15 @@ describe("StringDeclHelper", () => {
|
|
|
411
452
|
);
|
|
412
453
|
|
|
413
454
|
expect(result.handled).toBe(true);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
455
|
+
// Issue #1037: every line carries no indent of its own (the block emitter
|
|
456
|
+
// prefixes each line); continuation lines must not double-indent.
|
|
457
|
+
const concatLines = result.code.split("\n");
|
|
458
|
+
expect(concatLines[0]).toBe('char combined[33] = "";');
|
|
459
|
+
expect(concatLines[1]).toBe("strncpy(combined, str1, 32);");
|
|
460
|
+
expect(concatLines[2]).toBe(
|
|
461
|
+
"strncat(combined, str2, 32 - strlen(combined));",
|
|
418
462
|
);
|
|
419
|
-
expect(
|
|
463
|
+
expect(concatLines[3]).toBe("combined[32] = '\\0';");
|
|
420
464
|
});
|
|
421
465
|
|
|
422
466
|
it("throws error for concatenation at global scope", () => {
|
|
@@ -560,9 +604,11 @@ describe("StringDeclHelper", () => {
|
|
|
560
604
|
);
|
|
561
605
|
|
|
562
606
|
expect(result.handled).toBe(true);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
expect(
|
|
607
|
+
// Issue #1037: continuation lines carry no indent of their own.
|
|
608
|
+
const subLines = result.code.split("\n");
|
|
609
|
+
expect(subLines[0]).toBe('char sub[11] = "";');
|
|
610
|
+
expect(subLines[1]).toBe("strncpy(sub, srcStr + 0, 5);");
|
|
611
|
+
expect(subLines[2]).toBe("sub[5] = '\\0';");
|
|
566
612
|
});
|
|
567
613
|
|
|
568
614
|
it("throws error for substring at global scope", () => {
|